From 7fd81b3a4270b8e34e391d3da971bee517058657 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 00:57:03 +0000 Subject: [PATCH 1/7] Remove deprecated and redundant CLI commands - Remove deprecated runtasks command (replaced by configure) - Remove redundant recreate command (equivalent to delete + start) - Remove dev-only commands: test, clipboard, envvars, connect, fu - Remove poorly documented commands: writeconnectionevent, updatemodel - Remove user-specified commands: configure-env-vars, import-ide-config, secret - Remove hello onboarding command and references - Fix duplicate start command registration This cleanup improves CLI organization and removes unused/experimental functionality. All core commands (start, stop, delete, reset, login, logout, ls, shell, open) remain intact. Co-Authored-By: Alec Fong --- go.mod | 36 +- go.sum | 90 --- pkg/cmd/background/background.go | 182 ----- pkg/cmd/background/background_test.go | 1 - pkg/cmd/clipboard/clipboard.go | 105 --- pkg/cmd/clipboard/clipboard_listener.go | 71 -- pkg/cmd/cmd.go | 40 - pkg/cmd/configureenvvars/configureenvvars.go | 164 ---- .../configureenvvars/configureenvvars_test.go | 433 ----------- pkg/cmd/configureenvvars/lex.go | 266 ------- pkg/cmd/configureenvvars/lex_test.go | 710 ------------------ pkg/cmd/envvars/envvars.go | 33 - pkg/cmd/envvars/envvars_test.go | 1 - pkg/cmd/fu/fu.go | 107 --- pkg/cmd/fu/fu_test.go | 1 - pkg/cmd/hello/hello.go | 153 ---- pkg/cmd/hello/hello_test.go | 1 - pkg/cmd/hello/onboarding_utils.go | 271 ------- pkg/cmd/hello/steps.go | 326 -------- pkg/cmd/hello/updateUser.go | 120 --- pkg/cmd/importideconfig/importideconfig.go | 220 ------ .../importideconfig/importideconfig_test.go | 1 - pkg/cmd/login/login.go | 28 +- pkg/cmd/ls/ls.go | 33 - pkg/cmd/notebook/notebook.go | 11 +- pkg/cmd/ollama/ollama.go | 7 +- pkg/cmd/open/open.go | 5 - pkg/cmd/recreate/doc.md | 46 -- pkg/cmd/recreate/recreate.go | 226 ------ pkg/cmd/runtasks/doc.md | 42 -- pkg/cmd/runtasks/runtasks.go | 88 --- pkg/cmd/runtasks/runtasks_test.go | 1 - pkg/cmd/secret/secret.go | 204 ----- pkg/cmd/secret/secret_test.go | 1 - pkg/cmd/shell/shell.go | 8 +- pkg/cmd/test/test.go | 76 -- pkg/cmd/test/test_test.go | 1 - pkg/cmd/updatemodel/updatemodel.go | 422 ----------- pkg/cmd/updatemodel/updatemodel_test.go | 292 ------- .../writeconnectionevent.go | 44 -- .../writeconnectionevent_test.go | 41 - 41 files changed, 11 insertions(+), 4897 deletions(-) delete mode 100644 pkg/cmd/background/background.go delete mode 100644 pkg/cmd/background/background_test.go delete mode 100644 pkg/cmd/clipboard/clipboard.go delete mode 100644 pkg/cmd/clipboard/clipboard_listener.go delete mode 100644 pkg/cmd/configureenvvars/configureenvvars.go delete mode 100644 pkg/cmd/configureenvvars/configureenvvars_test.go delete mode 100644 pkg/cmd/configureenvvars/lex.go delete mode 100644 pkg/cmd/configureenvvars/lex_test.go delete mode 100644 pkg/cmd/envvars/envvars.go delete mode 100644 pkg/cmd/envvars/envvars_test.go delete mode 100644 pkg/cmd/fu/fu.go delete mode 100644 pkg/cmd/fu/fu_test.go delete mode 100644 pkg/cmd/hello/hello.go delete mode 100644 pkg/cmd/hello/hello_test.go delete mode 100644 pkg/cmd/hello/onboarding_utils.go delete mode 100644 pkg/cmd/hello/steps.go delete mode 100644 pkg/cmd/hello/updateUser.go delete mode 100644 pkg/cmd/importideconfig/importideconfig.go delete mode 100644 pkg/cmd/importideconfig/importideconfig_test.go delete mode 100644 pkg/cmd/recreate/doc.md delete mode 100644 pkg/cmd/recreate/recreate.go delete mode 100644 pkg/cmd/runtasks/doc.md delete mode 100644 pkg/cmd/runtasks/runtasks.go delete mode 100644 pkg/cmd/runtasks/runtasks_test.go delete mode 100644 pkg/cmd/secret/secret.go delete mode 100644 pkg/cmd/secret/secret_test.go delete mode 100644 pkg/cmd/test/test.go delete mode 100644 pkg/cmd/test/test_test.go delete mode 100644 pkg/cmd/updatemodel/updatemodel.go delete mode 100644 pkg/cmd/updatemodel/updatemodel_test.go delete mode 100644 pkg/cmd/writeconnectionevent/writeconnectionevent.go delete mode 100644 pkg/cmd/writeconnectionevent/writeconnectionevent_test.go diff --git a/go.mod b/go.mod index b84075da..1e17b0bd 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,6 @@ require ( github.com/briandowns/spinner v1.16.0 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.14.0 - github.com/gin-gonic/gin v1.10.0 - github.com/go-git/go-git/v5 v5.13.2 github.com/go-resty/resty/v2 v2.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 @@ -25,7 +23,6 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.1 - github.com/samber/lo v1.33.0 github.com/samber/mo v1.5.1 github.com/schollz/progressbar/v3 v3.9.0 github.com/sevlyar/go-daemon v0.1.5 @@ -44,62 +41,31 @@ require ( ) require ( - dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudflare/circl v1.4.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/gomega v1.34.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.19.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/tools v0.23.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apimachinery v0.31.1 // indirect k8s.io/client-go v0.31.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index 82f16edb..1354942c 100644 --- a/go.sum +++ b/go.sum @@ -35,24 +35,13 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -61,10 +50,6 @@ github.com/brevdev/parse v0.0.11 h1:OamoC1hKFW75ngzSQx9HHRh5bf/G6154Y9M2y4HNmIw= github.com/brevdev/parse v0.0.11/go.mod h1:ML13fBCP6yZsZearRnglD+6UlqkpiVN7Hjf8R9pd0TY= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -73,12 +58,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= -github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -86,18 +65,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= -github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -113,26 +86,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= -github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -145,20 +102,10 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -167,8 +114,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -264,8 +209,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.3.1 h1:aOXiD9oqiuLH8btPQW6SfgtQN5zwhyfzZls8a6sPJ/I= github.com/jedib0t/go-pretty/v6 v6.3.1/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= @@ -283,10 +226,6 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -296,8 +235,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -345,8 +282,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -365,8 +300,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk= -github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/samber/mo v1.5.1 h1:5dRSevAB33Q/OrYwTmtksHHxquuf2urnRSUTsdTFysY= github.com/samber/mo v1.5.1/go.mod h1:pDuQgWscOVGGoEz+NAeth/Xq+MPAcXxCeph1XIAm/DU= github.com/schollz/progressbar/v3 v3.9.0 h1:k9SRNQ8KZyibz1UZOaKxnkUE3iGtmGSDt1YY9KlCYQk= @@ -375,12 +308,9 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -412,8 +342,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -422,18 +350,12 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/wk8/go-ordered-map/v2 v2.0.0 h1:jWOAU/F5AkYb8jr/rkVPe418g7nf2CZBzyfOR4Y7Q1w= github.com/wk8/go-ordered-map/v2 v2.0.0/go.mod h1:fGIuB3GmY3JZP6L3t5riKtaSH9u13IYVYvar5Ee+9lM= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -450,9 +372,6 @@ go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93V go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -461,7 +380,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -499,8 +417,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -595,7 +511,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -607,7 +522,6 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -787,8 +701,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -818,9 +730,7 @@ k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7F k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/pkg/cmd/background/background.go b/pkg/cmd/background/background.go deleted file mode 100644 index 273f1f44..00000000 --- a/pkg/cmd/background/background.go +++ /dev/null @@ -1,182 +0,0 @@ -package background - -import ( - "bufio" - "fmt" - "log" - "os" - "os/exec" - "strings" - "time" - - "github.com/brevdev/brev-cli/pkg/analytics" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type BackgroundStore interface { - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - ModifyWorkspace(workspaceID string, options *store.ModifyWorkspaceRequest) (*entity.Workspace, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) -} - -func NewCmdBackground(t *terminal.Terminal, s BackgroundStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "background [flags] [command]", - Aliases: []string{"bg"}, - DisableFlagsInUseLine: true, - Short: "Run a command in the background with optional 'brev stop self' at the end", - Long: "This command will run a specified command in the background using nohup and write logs to $HOME/brev-background-logs.", - Example: "brev background ./myscript.sh --stop", - RunE: func(cmd *cobra.Command, args []string) error { - // Parse the flags - stopFlag, err := cmd.Flags().GetBool("stop") - if err != nil { - log.Fatal(err) - } - progressFlag, err := cmd.Flags().GetBool("progress") - if err != nil { - log.Fatal(err) - } - if progressFlag { - checkProgress() - return nil - } - // Join the args into a command string - command := "" - if len(args) > 0 { - command = args[0] - } - for i := 1; i < len(args); i++ { - command += " " + args[i] - } - // Create logs directory if it doesn't exist - logsDir := os.Getenv("HOME") + "/brev-background-logs" - err = os.MkdirAll(logsDir, os.ModePerm) - if err != nil { - log.Fatal(err) - } - - // Run the command in the background using nohup - c := exec.Command("nohup", "bash", "-c", command+">"+logsDir+"/log.txt 2>&1 &") // #nosec G204 - err = c.Start() - if err != nil { - log.Fatal(err) - } - // Write logs - logFile, err := os.OpenFile(logsDir+"/log.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) //nolint: gosec // TODO - if err != nil { - log.Fatal(err) - } - defer logFile.Close() //nolint: errcheck // TODO - logFile.WriteString(time.Now().Format("2006-01-02 15:04:05") + ": Command \"" + command + "\" was run in the background.\n") //nolint: errcheck,gosec // TODO - - // Write process details to data file - processesFile, err := os.OpenFile(logsDir+"/processes.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) //nolint: gosec // TODO - if err != nil { - log.Fatal(err) - } - defer processesFile.Close() //nolint: errcheck // TODO - _, _ = processesFile.WriteString(fmt.Sprintf("%d,%s,%s\n", c.Process.Pid, time.Now().Format("2006-01-02 15:04:05"), command)) - - if stopFlag { - // If --stop flag is set, run "brev stop self" at the end - defer func() { - stopCmd := exec.Command("brev", "stop", "self") - err = stopCmd.Run() - if err != nil { - log.Fatal(err) - } - }() - } - // Call analytics for open - _ = pushBackgroundAnalytics(s) - - t.Vprintf("Command \"%s\" has been run in the background. Check %s for logs.\n", command, logsDir) - return nil - }, - } - cmd.Flags().Bool("stop", false, "Stop the workspace after the command is finished") - cmd.Flags().Bool("progress", false, "Show progress of the background commands") - return cmd -} - -func pushBackgroundAnalytics(s BackgroundStore) error { - // Call analytics for open - userID := "" - user, err := s.GetCurrentUser() - if err != nil { - userID = "" - } else { - userID = user.ID - } - data := analytics.EventData{ - EventName: "Brev background", - UserID: userID, - } - err = analytics.TrackEvent(data) - return breverrors.WrapAndTrace(err) -} - -type Process struct { - ID int `json:"id"` - Command string `json:"command"` - LogsDir string `json:"logsDir"` - StartTime string `json:"startTime"` -} - -type Processes struct { - Processes []Process `json:"processes"` -} - -func checkProgress() { - filePath := fmt.Sprintf("%s/brev-background-logs/processes.txt", os.Getenv("HOME")) - file, err := os.Open(filePath) //nolint: gosec // TODO - if err != nil { - panic(err) - } - defer file.Close() //nolint: errcheck // TODO - - scanner := bufio.NewScanner(file) - - fmt.Println("Running processes:") - for scanner.Scan() { - processLine := scanner.Text() - processData := strings.Split(processLine, ",") - - startTime, err := time.Parse("2006-01-02 15:04:05", processData[1]) - if err != nil { - panic(err) - } - - // Check if the process is still running by its PID - processID := processData[0] - cmd := fmt.Sprintf("ps -p %s -o comm=", processID) - output, err := exec.Command("bash", "-c", cmd).Output() //nolint: gosec // TODO - - if err != nil || strings.TrimSpace(string(output)) == "" { - // Process is not running, show a checkmark - fmt.Printf("ID: %s \u2713\n", processID) - } else { - // Process is still running, display its info - fmt.Printf("ID: %s\n", processID) - fmt.Printf("Command: %s\n", processData[2]) - fmt.Printf("Start time: %s\n", startTime.Format("2006-01-02 15:04:05")) - } - - fmt.Println() - } - - if err := scanner.Err(); err != nil { - panic(err) - } -} diff --git a/pkg/cmd/background/background_test.go b/pkg/cmd/background/background_test.go deleted file mode 100644 index 313dbd6c..00000000 --- a/pkg/cmd/background/background_test.go +++ /dev/null @@ -1 +0,0 @@ -package background diff --git a/pkg/cmd/clipboard/clipboard.go b/pkg/cmd/clipboard/clipboard.go deleted file mode 100644 index 38857c81..00000000 --- a/pkg/cmd/clipboard/clipboard.go +++ /dev/null @@ -1,105 +0,0 @@ -package clipboard - -import ( - "fmt" - "os" - "os/exec" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - - "github.com/brevdev/brev-cli/pkg/cmd/portforward" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type ClipboardStore interface { - completions.CompletionStore -} - -// Step 1 -func EstablishConnection(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "connect", - DisableFlagsInUseLine: true, - Short: "Connects to remote", - Long: "Connects to remote", - Example: "connect", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Listen to Port - listener := CreateListener(&Config{ - Host: "127.0.0.1", - Port: "6969", - }) - listener.Run() - }, - } - return cmd -} - -func SaveToClipboard(output string) { - // copy to clipboard - command := fmt.Sprintf("echo %s | pbcopy", output) - cmd := exec.Command("bash", "-c", command) //nolint:gosec // testing - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Start() - if err != nil { - fmt.Println(err) - } - err1 := cmd.Wait() - if err1 != nil { - fmt.Println(err1) - } -} - -// Step 2 -func ForwardPort(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"ssh": ""}, - Use: "remote-forward ", - DisableFlagsInUseLine: true, - Short: "remote forward port", - Long: "remote forward port", - Example: "remote-forward", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(1)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Portforward - _, sshError := portforward.RunSSHPortForward("-R", "6969", "6969", args[0]) - if sshError != nil { - t.Errprint(sshError, "Failed to connect to local") - return - } - }, - } - return cmd -} - -// Step 3 -func SendToClipboard(t *terminal.Terminal, clipboardStore ClipboardStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"clipboard": ""}, - Use: "clipboard", - DisableFlagsInUseLine: true, - Short: "Copies clipboard from remote instance to local clipboard", - Long: "Copies clipboard from remote instance to local clipboard", - Example: "clipboard", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(clipboardStore, t), - Run: func(cmd *cobra.Command, args []string) { - // Send output - err := SendRequest("localhost:6969", "hello world") - if err != nil { - t.Errprint(err, "Failed to copy to clipboard") - return - } - }, - } - - return cmd -} diff --git a/pkg/cmd/clipboard/clipboard_listener.go b/pkg/cmd/clipboard/clipboard_listener.go deleted file mode 100644 index 38faadb2..00000000 --- a/pkg/cmd/clipboard/clipboard_listener.go +++ /dev/null @@ -1,71 +0,0 @@ -package clipboard - -import ( - "context" - "fmt" - "io/ioutil" - "net/http" - "strings" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/gin-gonic/gin" -) - -// Server ... -type Server struct { - host string - port string -} - -// Config ... -type Config struct { - Host string - Port string -} - -// New ... -func CreateListener(config *Config) *Server { - return &Server{ - host: config.Host, - port: config.Port, - } -} - -// tcp req -func SendRequest(address string, message string) error { - reader := strings.NewReader(message) - request, err := http.NewRequestWithContext(context.TODO(), "GET", "http://"+address+"/", reader) - if err != nil { - fmt.Println(err) - return breverrors.WrapAndTrace(err) - } - client := &http.Client{} - resp, err := client.Do(request) - fmt.Println(resp) - if err != nil { - fmt.Println(err) - return breverrors.WrapAndTrace(err) - } - defer resp.Body.Close() //nolint:errcheck //deving and defer - return nil -} - -// Run ... -func (server *Server) Run() { - // Starts a new Gin instance with no middle-ware - r := gin.New() - - r.GET("/", func(c *gin.Context) { - jsonData, err := ioutil.ReadAll(c.Request.Body) - if err != nil { - // Handle error - c.String(http.StatusBadRequest, "Can't parse body") - } - SaveToClipboard(string(jsonData)) - c.String(http.StatusOK, "success") - }) - err := r.Run(server.host + ":" + server.port) - if err != nil { - fmt.Println(err) - } -} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index f2d9a103..67f8e48e 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -5,18 +5,11 @@ import ( "fmt" "github.com/brevdev/brev-cli/pkg/auth" - "github.com/brevdev/brev-cli/pkg/cmd/background" - "github.com/brevdev/brev-cli/pkg/cmd/clipboard" - "github.com/brevdev/brev-cli/pkg/cmd/configureenvvars" "github.com/brevdev/brev-cli/pkg/cmd/connect" "github.com/brevdev/brev-cli/pkg/cmd/copy" "github.com/brevdev/brev-cli/pkg/cmd/create" "github.com/brevdev/brev-cli/pkg/cmd/delete" - "github.com/brevdev/brev-cli/pkg/cmd/envvars" - "github.com/brevdev/brev-cli/pkg/cmd/fu" "github.com/brevdev/brev-cli/pkg/cmd/healthcheck" - "github.com/brevdev/brev-cli/pkg/cmd/hello" - "github.com/brevdev/brev-cli/pkg/cmd/importideconfig" "github.com/brevdev/brev-cli/pkg/cmd/initfile" "github.com/brevdev/brev-cli/pkg/cmd/invite" "github.com/brevdev/brev-cli/pkg/cmd/login" @@ -29,12 +22,9 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/portforward" "github.com/brevdev/brev-cli/pkg/cmd/profile" "github.com/brevdev/brev-cli/pkg/cmd/proxy" - "github.com/brevdev/brev-cli/pkg/cmd/recreate" "github.com/brevdev/brev-cli/pkg/cmd/refresh" "github.com/brevdev/brev-cli/pkg/cmd/reset" - "github.com/brevdev/brev-cli/pkg/cmd/runtasks" "github.com/brevdev/brev-cli/pkg/cmd/scale" - "github.com/brevdev/brev-cli/pkg/cmd/secret" "github.com/brevdev/brev-cli/pkg/cmd/set" "github.com/brevdev/brev-cli/pkg/cmd/setupworkspace" "github.com/brevdev/brev-cli/pkg/cmd/shell" @@ -43,11 +33,8 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/status" "github.com/brevdev/brev-cli/pkg/cmd/stop" "github.com/brevdev/brev-cli/pkg/cmd/tasks" - "github.com/brevdev/brev-cli/pkg/cmd/test" - "github.com/brevdev/brev-cli/pkg/cmd/updatemodel" "github.com/brevdev/brev-cli/pkg/cmd/version" "github.com/brevdev/brev-cli/pkg/cmd/workspacegroups" - "github.com/brevdev/brev-cli/pkg/cmd/writeconnectionevent" "github.com/brevdev/brev-cli/pkg/config" "github.com/brevdev/brev-cli/pkg/featureflag" "github.com/brevdev/brev-cli/pkg/files" @@ -135,17 +122,6 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin Find more information at: https://brev.dev`, PostRun: func(cmd *cobra.Command, args []string) { - shouldWe := hello.ShouldWeRunOnboarding(noLoginCmdStore) - if shouldWe { - user, err := loginCmdStore.GetCurrentUser() - if err != nil { - return - } - err = hello.CanWeOnboard(t, user, loginCmdStore) - if err != nil { - return - } - } }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { breverrors.GetDefaultErrorReporter().AddTag("command", cmd.Name()) @@ -244,49 +220,33 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(tasks.NewCmdTasks(t, noLoginCmdStore)) cmd.AddCommand(tasks.NewCmdConfigure(t, noLoginCmdStore)) cmd.AddCommand(initfile.NewCmdInitFile(t, noLoginCmdStore)) - cmd.AddCommand(hello.NewCmdHello(t, noLoginCmdStore)) cmd.AddCommand(notebook.NewCmdNotebook(noLoginCmdStore, t)) // dev feature toggle if featureflag.IsDev() { _ = 0 // noop - cmd.AddCommand(test.NewCmdTest(t, noLoginCmdStore)) - cmd.AddCommand(clipboard.EstablishConnection(t, loginCmdStore)) - cmd.AddCommand(clipboard.SendToClipboard(t, loginCmdStore)) - cmd.AddCommand(clipboard.ForwardPort(t, loginCmdStore)) - cmd.AddCommand(envvars.NewCmdEnvVars(t, loginCmdStore)) cmd.AddCommand(connect.NewCmdConnect(t, noLoginCmdStore)) - cmd.AddCommand(fu.NewCmdFu(t, loginCmdStore, noLoginCmdStore)) } else { _ = 0 // noop } cmd.AddCommand(workspacegroups.NewCmdWorkspaceGroups(t, loginCmdStore)) cmd.AddCommand(scale.NewCmdScale(t, noLoginCmdStore)) - cmd.AddCommand(configureenvvars.NewCmdConfigureEnvVars(t, loginCmdStore)) - cmd.AddCommand(importideconfig.NewCmdImportIDEConfig(t, noLoginCmdStore)) cmd.AddCommand(shell.NewCmdShell(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(copy.NewCmdCopy(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(open.NewCmdOpen(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(ollama.NewCmdOllama(t, loginCmdStore)) - cmd.AddCommand(background.NewCmdBackground(t, loginCmdStore)) cmd.AddCommand(status.NewCmdStatus(t, loginCmdStore)) - cmd.AddCommand(secret.NewCmdSecret(loginCmdStore, t)) cmd.AddCommand(sshkeys.NewCmdSSHKeys(t, loginCmdStore)) cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(create.NewCmdCreate(t, loginCmdStore)) cmd.AddCommand(stop.NewCmdStop(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(delete.NewCmdDelete(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(reset.NewCmdReset(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore)) - cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore)) cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) cmd.AddCommand(setupworkspace.NewCmdSetupWorkspace(noLoginCmdStore)) - cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) - cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore)) - cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore)) } func hasQuickstartCommands(cmd *cobra.Command) bool { diff --git a/pkg/cmd/configureenvvars/configureenvvars.go b/pkg/cmd/configureenvvars/configureenvvars.go deleted file mode 100644 index da0a5af0..00000000 --- a/pkg/cmd/configureenvvars/configureenvvars.go +++ /dev/null @@ -1,164 +0,0 @@ -package configureenvvars - -import ( - "fmt" - "os" - "sort" - "strings" - - "github.com/alessio/shellescape" - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -const ( - BrevWorkspaceEnvPath = "/home/brev/workspace/.env" - BrevDevPlaneEnvPath = "/home/ubuntu/.brev/.env" - BrevManagedEnvVarsKey = "BREV_MANAGED_ENV_VARS" -) - -type envVars map[string]string - -type ConfigureEnvVarsStore interface { - GetFileAsString(path string) (string, error) -} - -func NewCmdConfigureEnvVars(_ *terminal.Terminal, cevStore ConfigureEnvVarsStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "configure-env-vars", - DisableFlagsInUseLine: true, - Short: "configure env vars in supported shells", - Long: "configure env vars in supported shells", - Example: "", - RunE: func(cmd *cobra.Command, args []string) error { - output, err := RunConfigureEnvVars(cevStore) - if err != nil { - // todo bubble up error, but in the meantime make sure there - // is no output - return nil - } - fmt.Print(output) - return nil - }, - } - - return cmd -} - -func RunConfigureEnvVars(cevStore ConfigureEnvVarsStore) (string, error) { - brevEnvsString := os.Getenv(BrevManagedEnvVarsKey) - // intentionally ignoring err - envFileContents, _ := cevStore.GetFileAsString(BrevWorkspaceEnvPath) - devplaneContents, _ := cevStore.GetFileAsString(BrevDevPlaneEnvPath) - envFileContents = envFileContents + "\n" + devplaneContents - return generateExportString(brevEnvsString, envFileContents), nil -} - -func generateExportString(brevEnvsString, envFileContents string) string { - if brevEnvsString == "" && envFileContents == "" { - return "" - } - brevEnvKeys := strings.Split(brevEnvsString, ",") - - envfileEntries := parse(envFileContents) - for key, val := range envfileEntries { - if !strings.HasPrefix(val, "'") { // already quoted - envfileEntries[key] = shellescape.Quote(val) - } - } - envFileKeys := keys(envfileEntries) - // sort to make tests consistent - sort.Slice(envFileKeys, func(i, j int) bool { - return envFileKeys[i] < envFileKeys[j] - }) - - // todo parameterize by shell - envCmdOutput := makeEnvCmdOutputLines(brevEnvKeys, envFileKeys, envfileEntries) - - return strings.Join(envCmdOutput, "\n") -} - -func makeEnvCmdOutputLines(brevEnvKeys, envFileKeys []string, envfileEntries envVars) []string { - envCmdOutput := []string{} - envCmdOutput = addUnsetEntriesToOutput(brevEnvKeys, envFileKeys, envCmdOutput) - envCmdOutput = append(envCmdOutput, addExportPrefix(envfileEntries)...) - newBrevEnvKeys := strings.Join(envFileKeys, ",") - newBrevEnvKeysEntry := "" - if newBrevEnvKeys != "" { - newBrevEnvKeysEntry = BrevManagedEnvVarsKey + "=" + newBrevEnvKeys - } - if newBrevEnvKeysEntry != "" { - envCmdOutput = append(envCmdOutput, "export "+newBrevEnvKeysEntry) - } - return collections.FilterEmpty(envCmdOutput) -} - -func addExportPrefix(envFile envVars) []string { - if len(envFile) == 0 { - return []string{} - } - out := []string{} - - // sorted order to make tests consistent - envFileKeys := keys(envFile) - for _, k := range envFileKeys { - out = append(out, fmt.Sprintf("%s %s=%s", "export", k, envFile[k])) - } - return out -} - -// return map's keys in sorted order -func keys(m map[string]string) []string { - out := []string{} - for k := range m { - out = append(out, k) - } - sort.Slice(out, func(i, j int) bool { - return out[i] < out[j] - }) - return out -} - -// this may be a good place to parameterize bby shell -func addUnsetEntriesToOutput(currentEnvs, newEnvs, output []string) []string { - for _, envKey := range currentEnvs { - if !collections.Contains(newEnvs, envKey) && envKey != "" { - output = append(output, "unset "+envKey) - } - } - return output -} - -// https://stackoverflow.com/a/38579502 -func zip(elements []string, elementMap map[string]string) map[string]string { - for i := 0; i < len(elements); i += 2 { - elementMap[elements[i]] = elements[i+1] - } - return elementMap -} - -func parse(content string) envVars { - keyValPairs := []string{} - lexer := lex("keys from env", content) - scanning := true - for scanning { - token := lexer.nextItem() - switch token.typ { - case itemKey, itemValue: - keyValPairs = append(keyValPairs, token.val) - case itemError: - return nil - case itemEOF: - scanning = false - - } - - } - if len(keyValPairs)%2 != 0 { - return nil - } - - return zip(keyValPairs, make(envVars)) -} diff --git a/pkg/cmd/configureenvvars/configureenvvars_test.go b/pkg/cmd/configureenvvars/configureenvvars_test.go deleted file mode 100644 index 01272f04..00000000 --- a/pkg/cmd/configureenvvars/configureenvvars_test.go +++ /dev/null @@ -1,433 +0,0 @@ -package configureenvvars - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func Test_generateExportString(t *testing.T) { //nolint:funlen // this is a test - type args struct { - brevEnvsString string - envFileContents string - } - tests := []struct { - name string - args args - want string - }{ - // TODO: Add test cases. - { - name: "base case", - args: args{ - brevEnvsString: "", - envFileContents: "", - }, - want: "", - }, - // TODO: Add test cases. - { - name: "deletes env vars not in envfile", - args: args{ - brevEnvsString: "foo,bar,baz", - envFileContents: "", - }, - want: `unset foo -unset bar -unset baz`, - }, - { - name: "sets env var", - args: args{ - brevEnvsString: "", - envFileContents: "foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + `=foo`, - }, - { - name: "sets env var with export prefix", - args: args{ - brevEnvsString: "", - envFileContents: "export foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + `=foo`, - }, - { - name: "is idempotent", - args: args{ - brevEnvsString: "foo", - envFileContents: "foo=bar", - }, - want: `export foo=bar -export ` + BrevManagedEnvVarsKey + "=foo", - }, - { - name: "multiple operations(journal) case", - args: args{ - brevEnvsString: "key1,key2,key3", - envFileContents: "export key4=val", - }, - want: `unset key1 -unset key2 -unset key3 -export key4=val -export ` + BrevManagedEnvVarsKey + "=key4", - }, - { - name: "using env format found on workspace", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar';export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "multi line file", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar'; -export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "semicolon -> newline file ", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='bar'; - -export alice='bob'`, - }, - want: `export alice='bob' -export foo='bar' -export ` + BrevManagedEnvVarsKey + "=alice,foo", - }, - { - name: "hyphen in env var shouldn't be included since that's not allowed in most shells", - args: args{ - brevEnvsString: "", - envFileContents: `export NADER-TEST='nader-testing' `, - }, - want: ``, - }, - { - name: "if we have an invalid env var, we should not include it", - args: args{ - brevEnvsString: "", - envFileContents: `export f$*;_=nader-testing`, - }, - want: ``, - }, - { - name: "invalid keys should be ignored", - args: args{ - brevEnvsString: "", - envFileContents: `export f$*;=nader-testing`, - }, - want: ``, - }, - { - name: "values are escaped", - args: args{ - brevEnvsString: "", - envFileContents: `export foo='90ie&$>'`, - }, - want: `export foo='90ie&$>' -export ` + BrevManagedEnvVarsKey + "=foo", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := generateExportString(tt.args.brevEnvsString, tt.args.envFileContents) - diff := cmp.Diff(tt.want, got) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} - -func Test_addUnsetEntriesToOutput(t *testing.T) { - type args struct { - currentEnvs []string - newEnvs []string - output []string - } - tests := []struct { - name string - args args - want []string - }{ - { - name: "base case", - args: args{ - currentEnvs: []string{}, - newEnvs: []string{}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "base case with empty strings", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{""}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "preserves output", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{""}, - output: []string{""}, - }, - want: []string{""}, - }, - { - name: "when a current env is not in the list of new envs, unset it", - args: args{ - currentEnvs: []string{"foo"}, - newEnvs: []string{}, - output: []string{}, - }, - want: []string{"unset foo"}, - }, - { - name: "when a current env is new envs, don't unset it", - args: args{ - currentEnvs: []string{}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "when a current env is enpty entry, don't unset it", - args: args{ - currentEnvs: []string{""}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - { - name: "when a current env is new envs and current envs, don't unset it", - args: args{ - currentEnvs: []string{"foo"}, - newEnvs: []string{"foo"}, - output: []string{}, - }, - want: []string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := addUnsetEntriesToOutput(tt.args.currentEnvs, tt.args.newEnvs, tt.args.output) - diff := cmp.Diff(tt.want, got) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} - -func Test_parse(t *testing.T) { //nolint:funlen // this is a test - type args struct { - content string - } - tests := []struct { - name string - args args - want envVars - }{ - // TODO: Add test cases. - { - name: "base case", - args: args{ - content: "", - }, - want: envVars{}, - }, - { - name: "parses envs", - args: args{ - content: "foo=bar", - }, - want: envVars{"foo": "bar"}, - }, - { - name: "parses envs other format", - args: args{ - content: "export foo='bar';export alice='bob'", - }, - want: envVars{"foo": "'bar'", "alice": "'bob'"}, - }, - { - name: "export prefixed file works ", - args: args{ - content: `export foo=bar`, - }, - want: envVars{"foo": "bar"}, - }, - { - name: "multi line file works", - args: args{ - content: `export foo=bar -export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "multi newline file works", - args: args{ - content: `export foo=bar - -export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "env var with space in val", - args: args{ - content: `export fo o=bar`, - }, - want: nil, - }, - { - name: "env var with space in key", - args: args{ - content: `export foo=ba r`, - }, - want: nil, - }, - { - name: "leading spaces works", - args: args{ - content: ` export foo=bar - export alice=bob`, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "trailing spaces works", - args: args{ - content: `export foo=bar -export alice=bob `, - }, - want: envVars{"foo": "bar", "alice": "bob"}, - }, - { - name: "export as key works", - args: args{ - content: `export foo=bar -export export=bob`, - }, - want: envVars{"foo": "bar", "export": "bob"}, - }, - { - name: "export as key works", - args: args{ - content: `export=bar`, - }, - want: envVars{"export": "bar"}, - }, - { - name: "invalid chars not parsed", - args: args{ - content: `foo&bar>baz=foo\&bar\>baz`, - }, - want: nil, - }, - { - name: "escaped values are parsed", - args: args{ - content: `foo='foo&bar>baz'`, - }, - want: envVars{"foo": "'foo&bar>baz'"}, - }, - { - name: "unescaped values are parsed", - args: args{ - content: `foo=foo&bar>baz`, - }, - want: envVars{"foo": "foo&bar>baz"}, - }, - // todo add these cases in the correct formaat - // {"Empty", "", "", ""}, - // {"Emptyish", " ", "", ""}, - // {"OnlyComment", "# ...", "", ""}, - // {"OnlyCommentish", " # ...", "", ""}, - // {"EmptyValue", "FoO=", "FoO", ""}, - // {"EmptyValueComment", "F=# ...", "F", ""}, - // {"EmptyValueSpace", "F_O= ", "F_O", ""}, - // {"EmptyValueSpaceComment", "F= # ...", "F", ""}, - // {"Simple", "FOO=bar", "FOO", "bar"}, - // {"Export", "export FOO=bar", "FOO", "bar"}, - // {"Spaces", " FOO = bar baz ", "FOO", "bar baz"}, - // {"Tabs", " FOO = bar ", "FOO", "bar"}, - // {"ExportSpaces", "export FOO = bar", "FOO", "bar"}, - // {"ExportAsKey", "export = bar", "export", "bar"}, - // {"Nums", "A1B2C3=a1b2c3", "A1B2C3", "a1b2c3"}, - // {"Comments", "FOO=bar # ok", "FOO", "bar"}, - // {"EmptyComments1", "FOO=#bar#", "FOO", ""}, - // {"EmptyComments2", "FOO= # bar ", "FOO", ""}, - // {"DoubleQuotes", `FOO="bar#"`, "FOO", "bar#"}, - // {"DoubleQuoteNewline", `FOO="bar\n"`, "FOO", "bar\n"}, - // {"DoubleQuoteNewlineComment", `FOO="bar\n" # comment`, "FOO", "bar\n"}, - // {"DoubleQuoteSpaces", `FOO = " bar\t" `, "FOO", " bar\t"}, - // {"SingleQuotes", "FOO='bar#'", "FOO", "bar#"}, - // {"SingleQuotesNewline", `FOO='\n' # empty`, "FOO", "\\n"}, - // {"SingleQuotesEmpty", "FOO='' # empty", "FOO", ""}, - // {"NormalSingleMix", "FOO=normal'single ' ", "FOO", "normalsingle "}, - // {"NormalDoubleMix", `FOO= "double\\" normal # "EOL"`, "FOO", "double\\ normal"}, - // {"AllModes", `export FOO = 'single\n' \\normal\t "double\"\n " # comment`, "FOO", "single\\n \\\\normal\\t double\"\n "}, - // {"UnicodeLiteral", "U1=\U0001F525", "U1", "\U0001F525"}, - // {"UnicodeLiteralQuoted", "U2= ' \U0001F525 ' ", "U2", " \U0001F525 "}, - // {"EscapedUnicode1byte", `U3="\u2318"`, "U3", "\U00002318"}, - // {"EscapedUnicode2byte", `U3="\uD83D\uDE01"`, "U3", "\U0001F601"}, - // {"EscapedUnicodeCombined", `U4="\u2318\uD83D\uDE01"`, "U4", "\U00002318\U0001F601"}, - // {"README.mdEscapedUnicode", `FOO="The template value\nmay have included\nsome newlines!\n\ud83d\udd25"`, "FOO", "The template value\nmay have included\nsome newlines!\n🔥"}, - // {"UnderscoreKey", "_=x' ' ", "_", "x "}, - // {"DottedKey", "FOO.BAR=x", "FOO.BAR", "x"}, - // {"FwdSlashedKey", "FOO/BAR=x", "FOO/BAR", "x"}, - // {"README.md", `SOME_KEY = normal unquoted \text 'plus single quoted\' "\"double quoted " # EOL`, "SOME_KEY", `normal unquoted \text plus single quoted\ "double quoted `}, - // {"WindowsNewline", `w="\r\n"`, "w", "\r\n"}, - // {"MissingEqual", "foo bar", ErrMissingSeparator, ""}, - // {"EmptyKey", "=bar", ErrEmptyKey, ""}, - // {"EqualOnly", "=", ErrEmptyKey, ""}, - // {"InvalidKey", "1abc=x", nil, "key"}, - // {"InvalidKey2", "@abc=x", nil, "key"}, - // {"InvalidKey3", "a b c=x", nil, "key"}, - // {"InvalidKey4", "a\nb=x", nil, "key"}, - // {"InvalidValue", "FOO=\x00", nil, "value"}, - // {"OpenDoubleQuote", `FOO=" bar`, ErrUnmatchedDouble, ""}, - // {"OpenSingleQuote", `FOO=' bar`, ErrUnmatchedSingle, ""}, - // {"UnmatchedMix", `FOO=ok '"ok"' \"not ok ''`, ErrUnmatchedDouble, ""}, - // {"UnmatchedMix2", `FOO=ok '"ok"' \"not ok '"'`, ErrUnmatchedSingle, ""}, - // {"InvalidEscape", `FOO="\a"`, nil, `"a"`}, - // {"IncompleteEscape", `FOO="\`, ErrIncompleteEscape, ""}, - // {"IncompleteHex", `FOO="\u12"`, ErrIncompleteHex, ""}, - // {"InvalidHex", `FOO="\uabcZ"`, nil, `"Z"`}, - // {"IncompleteSurrogatePair1", `FOO="abc \uD83D"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair2", `FOO="abc \uD83D \uDE01"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair3", `FOO="abc \uD83DDE01"`, ErrIncompleteSur, ""}, - // {"IncompleteSurrogatePair4", `FOO="abc \uD83D\uDE0"`, nil, `"\""`}, - - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := parse(tt.args.content); !reflect.DeepEqual(got, tt.want) { - t.Errorf("parse() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/cmd/configureenvvars/lex.go b/pkg/cmd/configureenvvars/lex.go deleted file mode 100644 index 89926ca7..00000000 --- a/pkg/cmd/configureenvvars/lex.go +++ /dev/null @@ -1,266 +0,0 @@ -package configureenvvars - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -type itemType int - -const ( - itemError itemType = iota - itemKey - itemValue - itemEquals - itemSemiColon - itemNewline - itemSpace - itemTab - itemEOF -) - -type item struct { - typ itemType - val string -} - -func (i item) String() string { - switch { - case i.typ == itemEOF: - return "EOF" - case i.typ == itemError: - return i.val - } - return fmt.Sprintf("<%s>", i.val) -} - -// a function that returns a statefn -type stateFn func(*lexer) stateFn - -type lexer struct { - name string // used in error reports - input string // string being scanned - start int // start position of this item - pos int // current position of this item - width int // width of the last rune read - items chan item // last scanned item - state stateFn -} - -func lex(name, input string) *lexer { - l := &lexer{ - name: name, - input: input, - state: lexText, - items: make(chan item, 2), - } - go l.run() // concurrently begin lexing - return l -} - -// synchronously receive an item from lexer -func (l *lexer) nextItem() item { - return <-l.items -} - -func (l *lexer) run() { - for state := lexText; state != nil; { - state = state(l) - } - close(l.items) // no more tokens will be delivered -} - -func (l *lexer) emit(t itemType) { - l.items <- item{t, l.input[l.start:l.pos]} - l.start = l.pos -} - -const eof = -1 - -// next returns the next rune in the input. -func (l *lexer) next() rune { - if l.pos >= len(l.input) { - l.width = 0 - return eof - } - r, w := utf8.DecodeRuneInString(l.input[l.pos:]) - l.width = w - l.pos += l.width - return r -} - -// peek returns but does not consume the next rune in the input. -func (l *lexer) peek() rune { - r := l.next() - l.backup() - return r -} - -// backup steps back one rune. Can only be called once per call of next. -func (l *lexer) backup() { - l.pos -= l.width -} - -func (l *lexer) errorf(format string, args ...interface{}) stateFn { - l.items <- item{ - itemError, - fmt.Sprintf(format, args...), - } - return nil -} - -const ( - equalPrefix = "=" - space = " " - tab = "\t" -) - -func lexText(l *lexer) stateFn { - for { - if strings.HasPrefix(l.input[l.pos:], tab) { - return lexTab - } - if strings.HasPrefix(l.input[l.pos:], space) { - return lexSpace - } - if strings.HasPrefix(l.input[l.pos:], newline) { - return lexNewline - } - if strings.HasPrefix(l.input[l.pos:], semicolon) { - return lexSemiColon - } - if strings.HasPrefix(l.input[l.pos:], equalPrefix) { - return lexKey // next state - } - if l.next() == eof { - break - } - } - if len(l.input[l.start:l.pos]) != 0 { - return l.errorf("unexpected eof") - } - l.emit(itemEOF) - return nil -} - -func lexKey(l *lexer) stateFn { - s := l.input[l.start:l.pos] - // determine if s alphanumeric or an underscore - // https://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names - for _, r := range s { - if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') { - return l.errorf("unexpected key character: %q", r) - } - } - - l.emit(itemKey) - return lexEquals -} - -func lexEquals(l *lexer) stateFn { - l.next() - l.emit(itemEquals) - r := l.peek() - switch r { - case '\'', '"': - return lexQuotedValue - default: - return lexValue - } -} - -func lexSemiColon(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != semicolon { - return l.errorf("unexpected semicolon") - } - l.emit(itemSemiColon) - return lexText -} - -func lexNewline(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != "\n" { - return l.errorf("unexpected newline") - } - l.emit(itemNewline) - return lexText -} - -func lexSpace(l *lexer) stateFn { - if strings.HasPrefix(l.input[l.start:l.pos], "export") { - l.start += len("export") - } - l.next() - if l.input[l.start:l.pos] != space { - return l.errorf("unexpected space") - } - l.emit(itemSpace) - return lexText -} - -func lexTab(l *lexer) stateFn { - l.next() - if l.input[l.start:l.pos] != tab { - return l.errorf("unexpected tab") - } - l.emit(itemTab) - return lexText -} - -const ( - semicolon = ";" - newline = "\n" -) - -func lexValue(l *lexer) stateFn { - for { - if strings.HasPrefix(l.input[l.pos:], semicolon) { - l.emit(itemValue) - return lexSemiColon - } - if strings.HasPrefix(l.input[l.pos:], newline) { - l.emit(itemValue) - return lexNewline - - } - if strings.HasPrefix(l.input[l.pos:], space) { - l.emit(itemValue) - return lexText - - } - if strings.HasPrefix(l.input[l.pos:], tab) { - l.emit(itemValue) - return lexText - - } - if l.next() == eof { - l.emit(itemValue) - l.emit(itemEOF) - return nil - } - } -} - -func lexQuotedValue(l *lexer) stateFn { - endQuote := map[rune]string{ - '\'': "'", - '"': "\"", - }[l.next()] - for { - if strings.HasPrefix(l.input[l.pos:], newline) { - return l.errorf("unexpected newline") - } - if strings.HasPrefix(l.input[l.pos:], endQuote) { - l.next() - l.emit(itemValue) - return lexText - } - - if l.next() == eof { - l.errorf("unexpected eof") - } - } -} diff --git a/pkg/cmd/configureenvvars/lex_test.go b/pkg/cmd/configureenvvars/lex_test.go deleted file mode 100644 index 56cc3a7a..00000000 --- a/pkg/cmd/configureenvvars/lex_test.go +++ /dev/null @@ -1,710 +0,0 @@ -package configureenvvars - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func Test_lex(t *testing.T) { //nolint:funlen // this is a test - type args struct { - input string - } - tests := []struct { - name string - args args - want []item - }{ - { - name: "base case", - args: args{ - input: "", - }, - want: []item{{ - typ: itemEOF, - val: "", - }}, - }, - { - name: "key=val works", - args: args{ - input: "key=val", - }, - want: []item{ - { - typ: itemKey, - val: "key", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "val", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "parses envs other format", - args: args{ - input: "export foo='bar';export alice='bob'", - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'bar'", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'bob'", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "export prefixed file works ", - args: args{ - input: `export foo=bar`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "multi line file works", - args: args{ - input: `export foo=bar -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "multi newline file works", - args: args{ - input: `export foo=bar - -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "semi colon -> newline file works", - args: args{ - input: `export foo=bar; - -export alice=bob`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "leading newline file works", - args: args{ - input: ` -export foo=bar; - -export alice=bob`, - }, - want: []item{ - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemNewline, - val: "\n", - }, - - { - typ: itemNewline, - val: "\n", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "alice", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bob", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "trailing space with semi colon at end", - args: args{ - input: `foo=bar ;`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemSemiColon, - val: ";", - }, - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "trailing space", - args: args{ - input: `foo=bar `, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - { - typ: itemSpace, - val: " ", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "leading space", - args: args{ - input: ` foo=bar`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "spaces in vals with quotes", - args: args{ - input: `foo='b ar'`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "'b ar'", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "spaces in vals without quotes", - args: args{ - input: `foo=b ar`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "b", - }, - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "spaces in vals without quotes, multiline", - args: args{ - input: `foo=b ar -alice=bob`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "b", - }, - { - typ: itemSpace, - val: " ", - }, - - { - typ: itemError, - val: "unexpected newline", - }, - }, - }, - { - name: "spaces in keys", - args: args{ - input: `fo o=bar`, - }, - want: []item{ - { - typ: itemError, - val: "unexpected space", - }, - }, - }, - { - name: "lower case export in env var name with space after doesn't screw things up", - args: args{ - input: `foexport o=bar`, - }, - want: []item{ - { - typ: itemError, - val: "unexpected space", - }, - }, - }, - { - name: "tabs instead of spaces", - args: args{ - input: ` foo=bar`, - }, - want: []item{ - { - typ: itemTab, - val: "\t", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "bar", - }, - - { - typ: itemEOF, - val: "", - }, - }, - }, - { - name: "tabs value", - args: args{ - input: ` foo=ba r`, - }, - want: []item{ - { - typ: itemTab, - val: "\t", - }, - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemValue, - val: "ba", - }, - { - typ: itemTab, - val: "\t", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "quoted value newline", - args: args{ - input: `foo="bar -"`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemError, - val: "unexpected newline", - }, - }, - }, - { - name: "quoted value eof", - args: args{ - input: `foo="bar`, - }, - want: []item{ - { - typ: itemKey, - val: "foo", - }, - { - typ: itemEquals, - val: "=", - }, - { - typ: itemError, - val: "unexpected eof", - }, - }, - }, - { - name: "don't include invalid env var names", - args: args{ - input: `export f$*;=nader-testing`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected semicolon", - }, - }, - }, - { - name: "don't include invalid env var names w/o semicolon", - args: args{ - input: `export f$*=nader-testing`, - }, - want: []item{ - { - typ: itemSpace, - val: " ", - }, - { - typ: itemError, - val: "unexpected key character: '$'", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := lex(tt.name, tt.args.input) - out := []item{} - - for { - token := got.nextItem() - out = append(out, token) - if token.typ == itemEOF || token.typ == itemError { - break - } - - } - diff := cmp.Diff(out, tt.want, cmp.AllowUnexported(item{})) - if diff != "" { - t.Fatalf(diff) - } - }) - } -} diff --git a/pkg/cmd/envvars/envvars.go b/pkg/cmd/envvars/envvars.go deleted file mode 100644 index aaa8d096..00000000 --- a/pkg/cmd/envvars/envvars.go +++ /dev/null @@ -1,33 +0,0 @@ -package envvars - -import ( - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -type EnvVarsStore interface{} - -func NewCmdEnvVars(_ *terminal.Terminal, evStore EnvVarsStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "env-vars", - DisableFlagsInUseLine: true, - Short: "configure env vars in supported shells", - Long: "Import your IDE config", - Example: "", - RunE: func(cmd *cobra.Command, args []string) error { - err := RunEnvVars(evStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func RunEnvVars(_ EnvVarsStore) error { - return nil -} diff --git a/pkg/cmd/envvars/envvars_test.go b/pkg/cmd/envvars/envvars_test.go deleted file mode 100644 index 7cbaf8e6..00000000 --- a/pkg/cmd/envvars/envvars_test.go +++ /dev/null @@ -1 +0,0 @@ -package envvars diff --git a/pkg/cmd/fu/fu.go b/pkg/cmd/fu/fu.go deleted file mode 100644 index d61f5be7..00000000 --- a/pkg/cmd/fu/fu.go +++ /dev/null @@ -1,107 +0,0 @@ -package fu - -import ( - "fmt" - "time" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/hashicorp/go-multierror" - "github.com/spf13/cobra" - stripmd "github.com/writeas/go-strip-markdown" -) - -var ( - fuLong string - fuExample = "brev fu " -) - -type FuStore interface { - completions.CompletionStore - DeleteWorkspace(workspaceID string) (*entity.Workspace, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - BanUser(userID string) error - GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) - GetAllOrgsAsAdmin(userID string) ([]entity.Organization, error) -} - -func NewCmdFu(t *terminal.Terminal, loginFuStore FuStore, noLoginFuStore FuStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "fu", - DisableFlagsInUseLine: true, - Short: "Fetch all workspaces for a user and delete them", - Long: stripmd.Strip(fuLong), - Example: fuExample, - ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginFuStore, t), - RunE: func(cmd *cobra.Command, args []string) error { - var allError error - for _, userID := range args { - err := fuUser(userID, t, loginFuStore) - if err != nil { - allError = multierror.Append(allError, err) - } - } - if allError != nil { - return breverrors.WrapAndTrace(allError) - } - return nil - }, - } - - return cmd -} - -func fuUser(userID string, t *terminal.Terminal, fuStore FuStore) error { - orgs, err := fuStore.GetAllOrgsAsAdmin(userID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var allWorkspaces []entity.Workspace - for _, org := range orgs { - workspaces, errr := fuStore.GetWorkspaces(org.ID, nil) - if errr != nil { - return breverrors.WrapAndTrace(errr) - } - allWorkspaces = append(allWorkspaces, workspaces...) - } - - s := t.NewSpinner() - s.Suffix = " Fetching workspaces for user " + userID - s.Start() - time.Sleep(5 * time.Second) - s.Stop() - - confirm := terminal.PromptGetInput(terminal.PromptContent{ - Label: fmt.Sprintf("Are you sure you want to delete all %d workspaces for user %s? (y/n)", len(allWorkspaces), userID), - ErrorMsg: "You must confirm to proceed.", - AllowEmpty: false, - }) - if confirm != "y" { - return nil - } - - for _, workspace := range allWorkspaces { - _, err2 := fuStore.DeleteWorkspace(workspace.ID) - if err2 != nil { - t.Vprintf(t.Red("Failed to delete workspace with ID: %s\n", workspace.ID)) - t.Vprintf(t.Red("Error: %s\n", err.Error())) - continue - } - t.Vprintf("✅ Deleted workspace %s\n", workspace.Name) - } - - err = fuStore.BanUser(userID) - if err != nil { - t.Vprintf(t.Red("Failed to ban user with ID: %s\n", userID)) - t.Vprintf(t.Red("Error: %s\n", err.Error())) - } - t.Vprint("\n") - t.Vprintf("🖕 Banned user %s\n", userID) - - return nil -} diff --git a/pkg/cmd/fu/fu_test.go b/pkg/cmd/fu/fu_test.go deleted file mode 100644 index 5818a26b..00000000 --- a/pkg/cmd/fu/fu_test.go +++ /dev/null @@ -1 +0,0 @@ -package fu diff --git a/pkg/cmd/hello/hello.go b/pkg/cmd/hello/hello.go deleted file mode 100644 index 7d6af6ba..00000000 --- a/pkg/cmd/hello/hello.go +++ /dev/null @@ -1,153 +0,0 @@ -package hello - -import ( - "fmt" - "sync" - "time" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type HelloStore interface { - GetAllWorkspaces(options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetCurrentUser() (*entity.User, error) - UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) - GetCurrentWorkspaceID() (string, error) -} - -func NewCmdHello(t *terminal.Terminal, store HelloStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "hello", - DisableFlagsInUseLine: true, - Long: "Get a quick onboarding of the Brev CLI", - Short: "Get a quick onboarding of the Brev CLI", - Example: "brev hello", - RunE: func(cmd *cobra.Command, args []string) error { - // terminal.DisplayBrevLogo(t) - t.Vprint("\n") - - user, err := store.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = RunOnboarding(t, user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func TypeItToMe(s string) { - sleepSpeed := 27 - - // // Make outgoing reader routine - // outgoing := make(chan string) - // go func() { - // inputReader := bufio.NewReader(os.Stdin) - // for { - // o, err := inputReader.ReadString('\n') - // if err != nil { - // fmt.Printf("outgoing error: %v", err) - // return - // } - // outgoing <- o - // } - // }() - - // ctx, cancel := context.WithCancel(context.Background()) - // defer ctx.Done() - // interrupt := make(chan os.Signal, 1) - // signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) - - // go func() { - // for { - // select { - // case <-outgoing: - // sleepSpeed /= 2 - - // case <-interrupt: - // sleepSpeed = 0 - - // case <-ctx.Done(): - // cancel() - // } - // } - // }() - - sRunes := []rune(s) - for i := 0; i < len(sRunes); i++ { - time.Sleep(time.Duration(sleepSpeed) * time.Millisecond) - fmt.Printf("%c", sRunes[i]) - } -} - -func TypeItToMeUnskippable(s string) { - sRunes := []rune(s) - for i := 0; i < len(sRunes); i++ { - time.Sleep(37 * time.Millisecond) - - fmt.Printf("%c", sRunes[i]) - } -} - -func TypeItToMeUnskippable27(s string) { - sRunes := []rune(s) - for i := 0; i < len(sRunes); i++ { - time.Sleep(27 * time.Millisecond) - - fmt.Printf("%c", sRunes[i]) - } -} - -var wg sync.WaitGroup - -func RunOnboarding(t *terminal.Terminal, user *entity.User, store HelloStore) error { - // Reset the onboarding object to walk through the onboarding fresh - err := SetOnboardingObject(OnboardingObject{0, false, false}) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - terminal.DisplayBrevLogo(t) - t.Vprint("\n") - - s := "Hey " + GetFirstName(user.Name) + " 👋\n" - - s += "\n\nI'm excited you installed NVIDIA Brev. Let's get you started!\n" - s += "\nbtw, reach out if you need anything" - s += t.Yellow("brev-support@nvidia.com") - - s += "\n\nNVIDIA Brev is a dev tool for creating and sharing GPU accelerated instances" - s += "\nRun " + t.Green("brev ls") + " to see your instances 👇\n" - - wg.Add(2) - go finishOutput(t, s) - go MarkOnboardingStepCompleted(t, user, store) - wg.Wait() - return nil -} - -func finishOutput(_ *terminal.Terminal, s string) { - TypeItToMe(s) - wg.Done() -} - -func MarkOnboardingStepCompleted(_ *terminal.Terminal, user *entity.User, store HelloStore) { - err := CompletedOnboardingIntro(user, store) - if err != nil { - // todo howto get this to sentry? - fmt.Printf("error marking onboarding step completed: %v", err) - } - wg.Done() -} diff --git a/pkg/cmd/hello/hello_test.go b/pkg/cmd/hello/hello_test.go deleted file mode 100644 index cd35a48c..00000000 --- a/pkg/cmd/hello/hello_test.go +++ /dev/null @@ -1 +0,0 @@ -package hello diff --git a/pkg/cmd/hello/onboarding_utils.go b/pkg/cmd/hello/onboarding_utils.go deleted file mode 100644 index 7ea80b2f..00000000 --- a/pkg/cmd/hello/onboarding_utils.go +++ /dev/null @@ -1,271 +0,0 @@ -package hello - -import ( - "os" - "path/filepath" - "strings" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/files" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/brev-cli/pkg/util" - "github.com/spf13/afero" -) - -func GetFirstName(name string) string { - appropriatelyCapitalized := strings.Title(strings.ToLower(name)) - split := strings.Split(appropriatelyCapitalized, " ") - if len(split) > 1 { - return split[0] - } - return name -} - -// The LS step should get the GetOnboardingData from the user -// and use that to check the step "FinishedOnboarding" -// Either way. It should set it to True -func ShouldWeRunOnboardingLSStep(s HelloStore) bool { - user, err := s.GetCurrentUser() - if err != nil { - return false - } - - ob, err := user.GetOnboardingData() - if err != nil { - return false - } - - if ob.FinishedOnboarding { - return false - } else { - // set the value and return true - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["finishedOnboarding"] = true - - user, err = s.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - // TODO: what should we do here? - return true - } - - return true - } -} - -func ShouldWeRunOnboarding(s HelloStore) bool { - workspaceID, err := s.GetCurrentWorkspaceID() - if err != nil { - return false - } - if workspaceID != "" { - return false - } - - oo, err := GetOnboardingObject() - if err != nil { - return true - } - if oo.Step == 0 && !oo.HasRunBrevOpen && !oo.HasRunBrevShell { - return true - } else { - return false - } -} - -func CanWeOnboard(t *terminal.Terminal, user *entity.User, store HelloStore) error { - s := t.Green("\n\nHi " + GetFirstName(user.Name) + "! Looks like it's your first time using Brev!\n") - - TypeItToMeUnskippable(s) - - res := terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Want a quick tour?", - ErrorMsg: "Please pick yes or no", - Items: []string{"Yes!", "No, I'll read docs later"}, - }) - if res == "Yes!" { - err := RunOnboarding(t, user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - _ = SetOnboardingObject(OnboardingObject{ - Step: 1, - HasRunBrevOpen: true, - HasRunBrevShell: true, - }) - - _ = SkippedOnboarding(user, store) - - t.Vprintf("\nOkay, you can always read the docs at %s\n\n", t.Yellow("https://brev.dev/docs")) - } - return nil -} - -func GetOnboardingFilePath() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", breverrors.WrapAndTrace(err) - } - path := files.GetOnboardingStepPath(home) - return path, nil -} - -type OnboardingObject struct { - Step int `json:"step"` - HasRunBrevShell bool `json:"hasRunBrevShell"` - HasRunBrevOpen bool `json:"hasRunBrevOpen"` -} - -func SetupDefaultOnboardingFile() error { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - exists, err := afero.Exists(files.AppFs, path) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !exists { - if err = files.AppFs.MkdirAll(filepath.Dir(path), 0o775); err != nil { - return breverrors.WrapAndTrace(err) - } - _, err = files.AppFs.Create(path) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - oo := OnboardingObject{0, false, false} - err = files.OverwriteJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - return nil -} - -func GetOnboardingObject() (*OnboardingObject, error) { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // Ensure file exists - err = SetupDefaultOnboardingFile() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // read file - var oo OnboardingObject - err = files.ReadJSON(files.AppFs, path, &oo) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // return data - return &oo, nil -} - -func SetOnboardingObject(oo OnboardingObject) error { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // Ensure file exists - err = SetupDefaultOnboardingFile() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // write file - err = files.OverwriteJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // return data - return nil -} - -// get path - -// Ensure file exists - -// write file - -// return data - -func SetHasRunShell(hasRunShell bool) error { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // Ensure file exists - err = SetupDefaultOnboardingFile() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // read file - var oo OnboardingObject - err = files.ReadJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // write file - oo.HasRunBrevShell = hasRunShell - err = files.OverwriteJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // return data - return nil -} - -func SetHasRunOpen(hasRunOpen bool) error { - // get path - path, err := GetOnboardingFilePath() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // Ensure file exists - err = SetupDefaultOnboardingFile() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // read file - var oo OnboardingObject - err = files.ReadJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // write file - oo.HasRunBrevOpen = hasRunOpen - err = files.OverwriteJSON(files.AppFs, path, &oo) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // return data - return nil -} diff --git a/pkg/cmd/hello/steps.go b/pkg/cmd/hello/steps.go deleted file mode 100644 index 74f606eb..00000000 --- a/pkg/cmd/hello/steps.go +++ /dev/null @@ -1,326 +0,0 @@ -package hello - -import ( - "fmt" - "time" - - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/brev-cli/pkg/util" - "github.com/briandowns/spinner" - "github.com/fatih/color" -) - -const DefaultDevEnvName = "first-workspace-react" - -const spinnerSuffix = "🎉 you did it!" - -func GetTextBasedONStatus(status string, t *terminal.Terminal) string { - s := "" - switch status { - case "RUNNING": - case "DEPLOYING": - s += t.Yellow("Your instance is deploying.") - s += "\nPlease wait for it to finish deploying then run " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - case "UNHEALTHY": - s += t.Red("Your instance seems stuck. Can you reach out to support?") - s += "\nMessage us " - s += "\n\t in discord 👉 " + t.Yellow("https://discord.gg/RpszWaJFRA") - s += "\n\t via text or call 👉 " + t.Yellow("(415) 237-2247\n") - s += "\n\nRun " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - case "STOPPED": - s += t.Yellow("Your instance is stopped.") - s += "\nRun this in your terminal to start it 👉 " + t.Yellow("brev start %s", DefaultDevEnvName) - s += "\n\nRun " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - - case "STOPPING": - s += t.Yellow("Your instance is stopped.") - s += "\nRun this in your terminal to start it 👉 " + t.Yellow("brev start %s", DefaultDevEnvName) - s += "\n\nRun " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - default: - s += t.Red("Please create a running instance for this walk through. ") - s += "\n\tYou can do that here: " + t.Yellow(fmt.Sprintf("%s/environments/new", config.ConsoleBaseURL)) - s += "\n\nRun " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - } - return s -} - -/* -Return nil to exit the onboarding -*/ -func GetDevEnvOrStall(t *terminal.Terminal, workspaces []entity.Workspace) *entity.Workspace { - var runningDevEnvs []entity.Workspace - noneFound := true - for _, v := range workspaces { - if v.Status == "RUNNING" { - noneFound = false - runningDevEnvs = append(runningDevEnvs, v) - } - } - - if noneFound { - s := t.Red("Please create a running instance for this walk through. ") - s += "\n\tYou can do that here: " + t.Yellow(fmt.Sprintf("%s/environments/new", config.ConsoleBaseURL)) - s += "\n\nRun: " + t.Yellow("brev hello") + " to resume this walk through when your instance is ready\n" - TypeItToMe(s) - return nil - } - msg := GetTextBasedONStatus(runningDevEnvs[0].Status, t) - if msg != "" { - TypeItToMe(msg) - } - return &runningDevEnvs[0] -} - -func printLsIntroText(t *terminal.Terminal, _ entity.Workspace) { - s := "\nThe command " + t.Yellow("brev ls") + " shows your instances" - s += "\nIf the instance is " + t.Green("RUNNING") + ", you can open it." - TypeItToMe(s) -} - -func printBrevShellOnboarding(t *terminal.Terminal, firstWorkspace *entity.Workspace) { - s := "\n\nTry opening a terminal SSHed in your instance" - s += "\nIn a new terminal, run " + t.Green("brev shell %s", firstWorkspace.Name) + "\n" - TypeItToMe(s) -} - -func printAskInstallVsCode(t *terminal.Terminal) { - // The error here is most likely because code isn't in path and we depend on that - // TODO: remove the dependency on code being in path - s := t.Yellow("\n\nCould you please install the following VSCode extension? %s", t.Green("ms-vscode-remote.remote-ssh")) - s += "\nDo that then run " + t.Yellow("brev hello") + " to resume this walk-through\n" - // s += "Here's a video of me installing the VS Code extension 👉 " + "" - TypeItToMe(s) -} - -func printBrevOpen(t *terminal.Terminal, firstWorkspace entity.Workspace) { - s := "\n\nTry opening VS Code in your instance" - s += "\nIn a new terminal, run " + t.Green("brev open %s", firstWorkspace.Name) + "\n" - TypeItToMe(s) -} - -func printCompletedOnboarding(t *terminal.Terminal) { - s := "\n\nI think I'm done here. Now you know how to open an instance and start coding." - s += "\n\nUse the console " + t.Yellow(fmt.Sprintf("(%s)", config.ConsoleBaseURL)) + " to create a new instance or share it with people" - s += "\nand use this CLI to code the way you would normally 🤙" - s += "\n\nCheck out the docs at " + t.Yellow("https://brev.dev") + " and let us know if we can help!\n" - s += "\n\nIn case you missed it, my cell is " + t.Yellow("(415) 237-2247") + "\n\t-Nader\n" - TypeItToMe(s) -} - -// func waitSpinner(spinner *spinner.Spinner) error { -// // a while loop in golang -// sum := 0 -// spinner.Suffix = "👆 try that, I'll wait" -// spinner.Start() -// for sum > -1 { -// sum++ - -// res, err2 := GetOnboardingObject() -// if err2 != nil { -// return breverrors.WrapAndTrace(err2) -// } -// if res.HasRunBrevShell { -// spinner.Suffix = spinnerSuffix -// time.Sleep(250 * time.Millisecond) -// spinner.Stop() -// break -// } -// time.Sleep(1 * time.Second) - -// } -// return nil -// } - -/* -Step 1: - - The user just ran brev ls -*/ -func Step1(t *terminal.Terminal, workspaces []entity.Workspace, user *entity.User, store HelloStore) error { - err := CompletedOnboardingLs(user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - spinner := t.NewSpinner() - bold := color.New(color.Bold).SprintFunc() - - firstWorkspace := GetDevEnvOrStall(t, workspaces) - if firstWorkspace == nil { - return nil - } - printLsIntroText(t, *firstWorkspace) - - // Check if VS Code is preferred editor - currentOnboardingStatus, err := user.GetOnboardingData() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if currentOnboardingStatus.Editor == "VSCode" { - err = doVsCodeOnboarding(t, firstWorkspace, user, store, spinner, bold) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err = doBrevShellOnboarding(t, firstWorkspace, user, store, spinner, bold) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - // err = waitSpinner(spinner) - // if err != nil { - // return breverrors.WrapAndTrace(err) - // } - - // err = CompletedOnboardingShell(user, store) - // if err != nil { - // return breverrors.WrapAndTrace(err) - // } - - // TypeItToMe("\nHit " + t.Yellow("enter") + " to continue") - // fmt.Println() - // _ = terminal.PromptGetInput(terminal.PromptContent{ - // // Label: " " + bold("▸") + " Press " + bold("Enter") + " to continue", - // Label: " " + bold("▸"), - // ErrorMsg: "error", - // AllowEmpty: true, - // }) - - // Commenting out the below since public urls is gone - // handleLocalhostURLIfDefaultProject(*firstWorkspace, t) - printCompletedOnboarding(t) - err = CompletedOnboarding(user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -// func handleLocalhostURLIfDefaultProject(ws entity.Workspace, t *terminal.Terminal) { -// if ws.Name == DefaultDevEnvName { -// s := "\n\nOne last thing, since you're coding in the cloud, you can get a public URL to your localhost." -// s += "\nFrom within that Brev dev environment,\n\tRun " + t.Yellow("npm run start") + " to spin up the service" -// s += "\nThen instead of going to localhost:3000, \n\tGo to " + t.Yellow("https://3000-%s", ws.DNS) - -// // TODO: Give that a shot then press enter -// bold := color.New(color.Bold).SprintFunc() - -// s += "\n\nGive that a shot then press enter👆:" -// TypeItToMe(s) - -// fmt.Print("\n") -// _ = terminal.PromptGetInput(terminal.PromptContent{ -// // Label: " " + bold("▸") + " Press " + bold("Enter") + " to continue", -// Label: " " + bold("▸"), -// ErrorMsg: "error", -// AllowEmpty: true, -// }) - -// fmt.Print("\n") -// } -// } -func doBrevShellOnboarding( - t *terminal.Terminal, - firstWorkspace *entity.Workspace, - user *entity.User, - store HelloStore, - spinner *spinner.Spinner, - bold func(a ...interface{}) string, -) error { - printBrevShellOnboarding(t, firstWorkspace) - - // a while loop in golang - sum := 0 - spinner.Suffix = "☝️ try that, I'll wait" - spinner.Start() - for sum < 1 { - sum += sum - res, err1 := GetOnboardingObject() - if err1 != nil { - return breverrors.WrapAndTrace(err1) - } - if res.HasRunBrevOpen { - spinner.Suffix = spinnerSuffix - time.Sleep(250 * time.Millisecond) - spinner.Stop() - break - } - time.Sleep(1 * time.Second) - - } - - err := CompletedOnboardingShell(user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - TypeItToMe("\nHit " + t.Yellow("enter") + " to continue") - fmt.Println() - - _ = terminal.PromptGetInput(terminal.PromptContent{ - // Label: " " + bold("▸") + " Press " + bold("Enter") + " to continue", - Label: " " + bold("▸"), - ErrorMsg: "error", - AllowEmpty: true, - }) - return nil -} - -func doVsCodeOnboarding( - t *terminal.Terminal, - firstWorkspace *entity.Workspace, - user *entity.User, - store HelloStore, - spinner *spinner.Spinner, - bold func(a ...interface{}) string, -) error { - // TODO: check if ext is installed - isInstalled, err := util.IsVSCodeExtensionInstalled("ms-vscode-remote.remote-ssh") - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !isInstalled { - printAskInstallVsCode(t) - return nil - } - - printBrevOpen(t, *firstWorkspace) - - sum := 0 - spinner.Suffix = "☝️ try that, I'll wait" - spinner.Start() - for sum < 1 { - sum += sum - res, err1 := GetOnboardingObject() - if err1 != nil { - return breverrors.WrapAndTrace(err1) - } - if res.HasRunBrevOpen { - spinner.Suffix = spinnerSuffix - time.Sleep(250 * time.Millisecond) - spinner.Stop() - break - } - time.Sleep(1 * time.Second) - - } - - err = CompletedOnboardingOpen(user, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - TypeItToMe("\nHit " + t.Yellow("enter") + " to continue") - fmt.Println() - - _ = terminal.PromptGetInput(terminal.PromptContent{ - // Label: " " + bold("▸") + " Press " + bold("Enter") + " to continue", - Label: " " + bold("▸"), - ErrorMsg: "error", - AllowEmpty: true, - }) - return nil -} diff --git a/pkg/cmd/hello/updateUser.go b/pkg/cmd/hello/updateUser.go deleted file mode 100644 index 37da6589..00000000 --- a/pkg/cmd/hello/updateUser.go +++ /dev/null @@ -1,120 +0,0 @@ -package hello - -import ( - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/util" -) - -// currentOnboardingStatus, err := user.GetOnboardingData() -// if err != nil { -// return breverrors.WrapAndTrace(err) -// } - -func SkippedOnboarding(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingSkipped"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func CompletedOnboardingIntro(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingIntro"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func CompletedOnboardingLs(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingLs"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func CompletedOnboardingShell(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingBrevShell"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func CompletedOnboardingOpen(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingBrevOpen"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} - -func CompletedOnboarding(user *entity.User, store HelloStore) error { - newOnboardingStatus := make(map[string]interface{}) - newOnboardingStatus["cliOnboardingCompleted"] = true - - _, err := store.UpdateUser(user.ID, &entity.UpdateUser{ - // username, name, and email are required fields, but we only care about onboarding status - Username: user.Username, - Name: user.Name, - Email: user.Email, - OnboardingData: util.MapAppend(user.OnboardingData, newOnboardingStatus), - }) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil -} diff --git a/pkg/cmd/importideconfig/importideconfig.go b/pkg/cmd/importideconfig/importideconfig.go deleted file mode 100644 index 1a891781..00000000 --- a/pkg/cmd/importideconfig/importideconfig.go +++ /dev/null @@ -1,220 +0,0 @@ -package importideconfig - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path" - "strings" - - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/hashicorp/go-multierror" - "github.com/tidwall/gjson" - "golang.org/x/text/encoding/charmap" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - - "github.com/spf13/cobra" -) - -// startLong = "[internal] test" -var startExample = "[internal] test" - -type ImportIDEConfigStore interface { - GetCurrentUser() (*entity.User, error) - UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) - GetWindowsDir() (string, error) -} - -func NewCmdImportIDEConfig(t *terminal.Terminal, s ImportIDEConfigStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "import-ide-config", - DisableFlagsInUseLine: true, - Short: "Import your IDE config", - Long: "Import your IDE config like vscode extensions", - Example: startExample, - // Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), - RunE: func(cmd *cobra.Command, args []string) error { - return breverrors.WrapAndTrace(RunImportIDEConfig(t, s)) - }, - } - - return cmd -} - -type ImportIDEConfigError struct { - Err error -} - -func (e *ImportIDEConfigError) Error() string { - return e.Err.Error() -} - -func importIDEConfigError(err error) error { - return &ImportIDEConfigError{Err: err} -} - -func RunImportIDEConfig(_ *terminal.Terminal, store ImportIDEConfigStore) error { - fmt.Println("updating vscode extensions...") - homedir, err := os.UserHomeDir() - if err != nil { - return breverrors.WrapAndTrace(importIDEConfigError(err)) - } - - windowsUserDir, _ := store.GetWindowsDir() - // if err != nil { - - // // todo multierror - // } - - var extensions []entity.VscodeExtensionMetadata - me := multierror.Append(nil) - for _, dir := range []string{homedir, windowsUserDir} { - if dir == "" { - continue - } - exts, err2 := getExtensions(dir) - if err2 != nil { - me = multierror.Append(me, err2) - } - extensions = append(extensions, exts...) - } - if me.ErrorOrNil() != nil && len(extensions) == 0 { - return breverrors.WrapAndTrace(importIDEConfigError(errors.New("no vscode extensions found"))) - } - - // todo print me (multierror) if not nil - user, err := store.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(importIDEConfigError(err)) - } - - _, err = store.UpdateUser(user.ID, &entity.UpdateUser{ - IdeConfig: entity.IDEConfig{ - VSCode: entity.VSCodeConfig{ - Extensions: extensions, - }, - }, - }) - if err != nil { - return breverrors.WrapAndTrace(importIDEConfigError(err)) - } - - return nil -} - -// Create a VSCodeMetadataObject from package.json file -func createVSCodeMetadataObject(homedir string, path string) (*entity.VscodeExtensionMetadata, error) { - segments := strings.Split(path, "/") - if !strings.Contains(segments[0], ".vscode") && - segments[1] != "extensions" && segments[3] != "package.json" { - return nil, errors.New("extension could not be imported") // TODO: return this as a metric!!! - } - contents, err := catFile(homedir + "/" + path) - if err != nil { - return nil, err - } else { - repoBlock := gjson.Get(contents, "repository").String() - repoURL := gjson.Get(repoBlock, "url").String() - return &entity.VscodeExtensionMetadata{ - Name: gjson.Get(contents, "name").String(), - DisplayName: gjson.Get(contents, "displayName").String(), - Version: gjson.Get(contents, "version").String(), - Publisher: gjson.Get(contents, "publisher").String(), - Description: gjson.Get(contents, "description").String(), - Repository: repoURL, - }, nil - } -} - -func catFile(filePath string) (string, error) { - gocmd := exec.Command("cat", filePath) // #nosec G204 - in, err := gocmd.Output() - if err != nil { - return "", breverrors.Wrap(err, "error reading file "+filePath) - } else { - d := charmap.CodePage850.NewDecoder() - out, err := d.Bytes(in) - if err != nil { - return "", breverrors.Wrap(err, "error reading file "+filePath) - } - return string(out), nil - } -} - -func appendPath(a string, b string) string { - if a == "." { - return b - } - return path.Join(a, b) -} - -func getExtensions(homedir string) ([]entity.VscodeExtensionMetadata, error) { - var extensions []entity.VscodeExtensionMetadata - - // intentionally reading from .vscode and not .vscode_extensions because if they want the extension, it should be installed locally - paths, err := recursivelyFindFile([]string{"package.json"}, homedir+"/.vscode/extensions") - if err != nil { - if strings.Contains(err.Error(), "no such file or directory") { - var err1 error - paths, err1 = recursivelyFindFile([]string{"package.json"}, homedir+"/.vscode-server/extensions") - if err1 != nil { - return nil, multierror.Append(err, err1) - } - } else { - return nil, breverrors.WrapAndTrace(err) - } - } - for _, v := range paths { - pathWithoutHome := strings.Split(v, homedir+"/")[1] - - // of the format - // .vscode / extensions / extension_name / package.json - // 1 2 3 4s - if len(strings.Split(pathWithoutHome, "/")) == 4 { - obj, err0 := createVSCodeMetadataObject(homedir, pathWithoutHome) - if err0 != nil { - return nil, err0 - } - extensions = append(extensions, *obj) - } - } - return extensions, nil -} - -// Returns list of paths to file -func recursivelyFindFile(filenames []string, path string) ([]string, error) { - var paths []string - - files, err := ioutil.ReadDir(path) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - for _, f := range files { - dir, err := os.Stat(appendPath(path, f.Name())) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } else { - for _, filename := range filenames { - if filename == f.Name() { - paths = append(paths, appendPath(path, f.Name())) - } - } - - if dir.IsDir() { - res, err := recursivelyFindFile(filenames, appendPath(path, f.Name())) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - paths = append(paths, res...) - } - } - } - - return paths, nil -} diff --git a/pkg/cmd/importideconfig/importideconfig_test.go b/pkg/cmd/importideconfig/importideconfig_test.go deleted file mode 100644 index f78f3fa8..00000000 --- a/pkg/cmd/importideconfig/importideconfig_test.go +++ /dev/null @@ -1 +0,0 @@ -package importideconfig diff --git a/pkg/cmd/login/login.go b/pkg/cmd/login/login.go index ffc32150..185be78c 100644 --- a/pkg/cmd/login/login.go +++ b/pkg/cmd/login/login.go @@ -9,16 +9,12 @@ import ( "github.com/brevdev/brev-cli/pkg/auth" "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "github.com/brevdev/brev-cli/pkg/cmd/hello" - - "github.com/brevdev/brev-cli/pkg/cmd/importideconfig" "github.com/brevdev/brev-cli/pkg/entity" breverrors "github.com/brevdev/brev-cli/pkg/errors" "github.com/brevdev/brev-cli/pkg/store" "github.com/brevdev/brev-cli/pkg/terminal" "github.com/brevdev/brev-cli/pkg/util" "github.com/fatih/color" - "github.com/hashicorp/go-multierror" "github.com/spf13/cobra" ) @@ -37,8 +33,6 @@ type LoginStore interface { GetServerSockFile() string GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) - hello.HelloStore - importideconfig.ImportIDEConfigStore UserHomeDir() (string, error) } @@ -67,33 +61,13 @@ func NewCmdLogin(t *terminal.Terminal, loginStore LoginStore, auth Auth) *cobra. Long: "Log into brev", Example: "brev login", PostRunE: func(cmd *cobra.Command, args []string) error { - shouldWe := hello.ShouldWeRunOnboarding(loginStore) - if shouldWe { - user, err := loginStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = hello.CanWeOnboard(t, user, loginStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } return nil }, Args: cmderrors.TransformToValidationError(cobra.NoArgs), RunE: func(cmd *cobra.Command, args []string) error { err := opts.RunLogin(t, loginToken, skipBrowser, emailFlag, authProviderFlag) if err != nil { - // if err is ImportIDEConfigError, log err with sentry but continue - if _, ok := err.(*importideconfig.ImportIDEConfigError); !ok { - return err - } - // todo alert sentry - err2 := RunTasksForUser(t) - if err2 != nil { - err = multierror.Append(err, err2) - } - return err //nolint:wrapcheck // we want to return the error from the login + return err } return nil }, diff --git a/pkg/cmd/ls/ls.go b/pkg/cmd/ls/ls.go index 78592c04..ee0e39e2 100644 --- a/pkg/cmd/ls/ls.go +++ b/pkg/cmd/ls/ls.go @@ -8,7 +8,6 @@ import ( "github.com/brevdev/brev-cli/pkg/analytics" "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/hello" utilities "github.com/brevdev/brev-cli/pkg/cmd/util" "github.com/brevdev/brev-cli/pkg/cmdcontext" "github.com/brevdev/brev-cli/pkg/config" @@ -31,7 +30,6 @@ type LsStore interface { GetUsers(queryParams map[string]string) ([]entity.User, error) GetWorkspace(workspaceID string) (*entity.Workspace, error) GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) - hello.HelloStore } func NewCmdLs(t *terminal.Terminal, loginLsStore LsStore, noLoginLsStore LsStore) *cobra.Command { @@ -50,37 +48,6 @@ func NewCmdLs(t *terminal.Terminal, loginLsStore LsStore, noLoginLsStore LsStore brev ls --org `, PersistentPostRunE: func(cmd *cobra.Command, args []string) error { - if hello.ShouldWeRunOnboardingLSStep(noLoginLsStore) && hello.ShouldWeRunOnboarding(noLoginLsStore) { - // Getting the workspaces should go in the hello.go file but then - // requires passing in stores and that makes it hard to use in other commands - org, err := getOrgForRunLs(loginLsStore, org) - if err != nil { - return err - } - - allWorkspaces, err := loginLsStore.GetWorkspaces(org.ID, nil) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - user, err := loginLsStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var myWorkspaces []entity.Workspace - for _, v := range allWorkspaces { - if v.CreatedByUserID == user.ID { - myWorkspaces = append(myWorkspaces, v) - } - } - - err = hello.Step1(t, myWorkspaces, user, loginLsStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - } return nil }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { diff --git a/pkg/cmd/notebook/notebook.go b/pkg/cmd/notebook/notebook.go index 3a3bfbae..0ab871fe 100644 --- a/pkg/cmd/notebook/notebook.go +++ b/pkg/cmd/notebook/notebook.go @@ -1,7 +1,8 @@ package notebook import ( - "github.com/brevdev/brev-cli/pkg/cmd/hello" + "fmt" + "github.com/brevdev/brev-cli/pkg/cmd/portforward" "github.com/brevdev/brev-cli/pkg/cmd/util" "github.com/brevdev/brev-cli/pkg/entity" @@ -43,7 +44,6 @@ func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { }() // Type out the checking message - hello.TypeItToMeUnskippable27("Checking to make sure the workspace is running...") // Wait for the network call to finish result := <-resultCh @@ -54,16 +54,15 @@ func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { // Check if the workspace is running if result.Workspace.Status != "RUNNING" { - hello.TypeItToMeUnskippable27("The workspace is not running. Please ensure it's in the running state before proceeding.") return breverrors.WorkspaceNotRunning{Status: result.Workspace.Status} } urlType := color.New(color.FgCyan, color.Bold).SprintFunc() warningType := color.New(color.FgBlack, color.Bold, color.BgCyan).SprintFunc() - hello.TypeItToMeUnskippable("\n" + warningType(" Please keep this terminal open 🤙 ")) + fmt.Print("\n" + warningType(" Please keep this terminal open 🤙 ")) - hello.TypeItToMeUnskippable27("\nClick here to go to your Jupyter notebook:\n\t 👉" + urlType("http://localhost:8888") + "👈\n\n\n") + fmt.Print("\nClick here to go to your Jupyter notebook:\n\t 👉" + urlType("http://localhost:8888") + "👈\n\n\n") // Port forward on 8888 err2 := portforward.RunPortforward(store, args[0], "8888:8888", false) @@ -72,7 +71,7 @@ func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { } // Print out a link for the user - hello.TypeItToMeUnskippable27("Your notebook is accessible at: http://localhost:8888") + fmt.Print("Your notebook is accessible at: http://localhost:8888") return nil }, diff --git a/pkg/cmd/ollama/ollama.go b/pkg/cmd/ollama/ollama.go index 0ebfc99a..c1c2585c 100644 --- a/pkg/cmd/ollama/ollama.go +++ b/pkg/cmd/ollama/ollama.go @@ -21,7 +21,6 @@ import ( "github.com/brevdev/brev-cli/pkg/terminal" "github.com/spf13/cobra" - "github.com/brevdev/brev-cli/pkg/cmd/hello" breverrors "github.com/brevdev/brev-cli/pkg/errors" ) @@ -114,7 +113,7 @@ func NewCmdOllama(t *terminal.Terminal, ollamaStore OllamaStore) *cobra.Command }) // Type out the creating workspace message - hello.TypeItToMeUnskippable27("🤙🦙🤙🦙🤙🦙🤙") + t.Vprint("🤙🦙🤙🦙🤙🦙🤙") t.Vprint(t.Green("\n\n\n")) _, err := res.Await() @@ -152,7 +151,7 @@ func runOllamaWorkspace(t *terminal.Terminal, opts RunOptions, ollamaStore Ollam instanceName := fmt.Sprintf("ollama-%s", uuid) cwOptions := store.NewCreateWorkspacesOptions(clusterID, instanceName).WithInstanceType(instanceType) - hello.TypeItToMeUnskippable27(fmt.Sprintf("Creating Ollama server %s with model %s in org %s\n", t.Green(cwOptions.Name), t.Green(opts.Model), t.Green(org.ID))) + t.Vprintf("Creating Ollama server %s with model %s in org %s\n", t.Green(cwOptions.Name), t.Green(opts.Model), t.Green(org.ID)) s := t.NewSpinner() @@ -181,7 +180,7 @@ func runOllamaWorkspace(t *terminal.Terminal, opts RunOptions, ollamaStore Ollam return breverrors.New("instance did not start") } s.Stop() - hello.TypeItToMeUnskippable27("VM is ready!\n") + t.Vprint("VM is ready!\n") s.Start() // TODO: look into timing of verb call diff --git a/pkg/cmd/open/open.go b/pkg/cmd/open/open.go index bbd5a185..ee902a80 100644 --- a/pkg/cmd/open/open.go +++ b/pkg/cmd/open/open.go @@ -12,7 +12,6 @@ import ( "github.com/brevdev/brev-cli/pkg/analytics" "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/hello" "github.com/brevdev/brev-cli/pkg/cmd/refresh" "github.com/brevdev/brev-cli/pkg/cmd/util" "github.com/brevdev/brev-cli/pkg/entity" @@ -194,10 +193,6 @@ func runOpenCommand(t *terminal.Terminal, tstore OpenStore, wsIDOrName string, s return breverrors.WrapAndTrace(err) } - err = hello.SetHasRunOpen(true) - if err != nil { - return breverrors.WrapAndTrace(err) - } // we don't care about the error here but should log with sentry // legacy environments wont support this and cause errrors, // but we don't want to block the user from using vscode diff --git a/pkg/cmd/recreate/doc.md b/pkg/cmd/recreate/doc.md deleted file mode 100644 index b8b4d8fc..00000000 --- a/pkg/cmd/recreate/doc.md +++ /dev/null @@ -1,46 +0,0 @@ -# Re Create Workspace by name or ID. - -## SYNOPSIS - -``` - brev recreate [ Workspace Name or ID... ] -``` - -## DESCRIPTION - -recreate a workspace is equivalent to running the following commands: - -``` -brev delete payments-fronted -brev start payments-frontend -``` - -This command has the effect of updating the base image of a workspace to the -latest. If your workspace has a git remote source, the workspace will start -with a fresh copy of the remote source and run the workspace setupscript. - -## EXAMPLE - -recreate a workspace with the name `naive-pubsub` - -``` -$ brev recreate payments-frontend -Starting hard reset 🤙 This can take a couple of minutes. - -Deleting workspace - naive-pubsub. -Workspace is starting. This can take up to 2 minutes the first time. -name naive-pubsub -template v7nd45zsc Admin -resource class 4x16 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⢿ workspace is deploying -Your workspace is ready! - -SSH into your machine: - ssh naive-pubsub-uq0x -``` - -## SEE ALSO - - TODO diff --git a/pkg/cmd/recreate/recreate.go b/pkg/cmd/recreate/recreate.go deleted file mode 100644 index aa0e0a01..00000000 --- a/pkg/cmd/recreate/recreate.go +++ /dev/null @@ -1,226 +0,0 @@ -// Package recreate is for the recreate command -package recreate - -import ( - _ "embed" - "strings" - "time" - - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/featureflag" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - stripmd "github.com/writeas/go-strip-markdown" -) - -//go:embed doc.md -var long string - -type recreateStore interface { - completions.CompletionStore - util.GetWorkspaceByNameOrIDErrStore - ResetWorkspace(workspaceID string) (*entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) - GetWorkspace(id string) (*entity.Workspace, error) - DeleteWorkspace(workspaceID string) (*entity.Workspace, error) -} - -func NewCmdRecreate(t *terminal.Terminal, store recreateStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "recreate", - DisableFlagsInUseLine: true, - Short: "TODO", - Long: stripmd.Strip(long), - Example: "TODO", - RunE: func(cmd *cobra.Command, args []string) error { - err := RunRecreate(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunRecreate(t *terminal.Terminal, args []string, recreateStore recreateStore) error { - for _, arg := range args { - err := hardResetProcess(arg, t, recreateStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -// hardResetProcess deletes an existing workspace and creates a new one -func hardResetProcess(workspaceName string, t *terminal.Terminal, recreateStore recreateStore) error { - t.Vprint(t.Green("recreating 🤙 " + t.Yellow("This can take a couple of minutes.\n"))) - workspace, err := util.GetUserWorkspaceByNameOrIDErr(recreateStore, workspaceName) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - deletedWorkspace, err := recreateStore.DeleteWorkspace(workspace.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Yellow("Deleting instance - %s.", deletedWorkspace.Name)) - time.Sleep(10 * time.Second) - - if len(deletedWorkspace.GitRepo) != 0 { - err := hardResetCreateWorkspaceFromRepo(t, recreateStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := hardResetCreateEmptyWorkspace(t, recreateStore, deletedWorkspace) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -// hardResetCreateWorkspaceFromRepo clone a GIT repository, triggeres from the --hardreset flag -func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.")) - var orgID string - activeorg, err := recreateStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name).WithGitRepo(workspace.GitRepo) - - user, err := recreateStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := recreateStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, recreateStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - return nil -} - -// hardResetCreateEmptyWorkspace creates a new empty worksapce, triggered from the --hardreset flag -func hardResetCreateEmptyWorkspace(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error { - t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.\n")) - - // ensure name - if len(workspace.Name) == 0 { - return breverrors.NewValidationError("name field is required for empty instances") - } - - // ensure org - var orgID string - activeorg, err := recreateStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if activeorg == nil { - return breverrors.NewValidationError("no org exist") - } - orgID = activeorg.ID - clusterID := config.GlobalConfig.GetDefaultClusterID() - options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name) - - user, err := recreateStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - options = resolveWorkspaceUserOptions(options, user) - - options.StartupScriptPath = workspace.StartupScriptPath - options.Execs = workspace.ExecsV0 - options.Repos = workspace.ReposV0 - options.IDEConfig = &workspace.IDEConfig - - w, err := recreateStore.CreateWorkspace(orgID, options) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = pollUntil(t, w.ID, entity.Running, recreateStore, true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - t.Vprint(t.Green("\nYour instance is ready!")) - t.Vprintf(t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier())) - - return nil -} - -func pollUntil(t *terminal.Terminal, wsid string, state string, recreateStore recreateStore, canSafelyExit bool) error { - s := t.NewSpinner() - isReady := false - if canSafelyExit { - t.Vprintf("You can safely ctrl+c to exit\n") - } - s.Suffix = " hang tight 🤙" - s.Start() - for !isReady { - time.Sleep(5 * time.Second) - ws, err := recreateStore.GetWorkspace(wsid) - if err != nil { - return breverrors.WrapAndTrace(err) - } - s.Suffix = " instance is " + strings.ToLower(ws.Status) - if ws.Status == state { - s.Suffix = "Instance is ready!" - s.Stop() - isReady = true - } - } - return nil -} - -func resolveWorkspaceUserOptions(options *store.CreateWorkspacesOptions, user *entity.User) *store.CreateWorkspacesOptions { - if options.WorkspaceTemplateID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceTemplateID = store.DevWorkspaceTemplateID - } else { - options.WorkspaceTemplateID = store.UserWorkspaceTemplateID - } - } - if options.WorkspaceClassID == "" { - if featureflag.IsAdmin(user.GlobalUserType) { - options.WorkspaceClassID = store.DevWorkspaceClassID - } else { - options.WorkspaceClassID = store.UserWorkspaceClassID - } - } - return options -} diff --git a/pkg/cmd/runtasks/doc.md b/pkg/cmd/runtasks/doc.md deleted file mode 100644 index a0200ae4..00000000 --- a/pkg/cmd/runtasks/doc.md +++ /dev/null @@ -1,42 +0,0 @@ -##### Synopsis - -``` - brev run-tasks -d -``` - -##### Description - -In order for brev to connect to workspaces, there needs to be background daemons -running to manage some things on your local machines environment. Currently, the -one that is being launched by run-tasks is an ssh config file configuration -daemon that periodically udpates a ssh config file with connection information -in order to access you workspaces. - -This command has to be run at every boot, see [Configuring SSH Proxy Daemon at Boot](https://docs.brev.dev/howto/configure-ssh-proxy-daemon-at-boot/) to -configure this command to be run at boot. - -This command is set to be deprecated in favor of `brev configure`. - -##### Examples - -to run tasks in the background - -``` -$ brev run-tasks -d -PID File: /home/f/.brev/task_daemon.pid -Log File: /home/f/.brev/task_daemon.log -``` - -to run tasks in the foreground - -``` -$ brev run-tasks -2022/07/11 15:28:44 creating new ssh config -2022/07/11 15:28:48 creating new ssh config - -``` - -##### See Also - -- [Configuring SSH Proxy Daemon at Boot](https://docs.brev.dev/howto/configure-ssh-proxy-daemon-at-boot/) - -TODO brev configure docs diff --git a/pkg/cmd/runtasks/runtasks.go b/pkg/cmd/runtasks/runtasks.go deleted file mode 100644 index 90e79cad..00000000 --- a/pkg/cmd/runtasks/runtasks.go +++ /dev/null @@ -1,88 +0,0 @@ -package runtasks - -import ( - _ "embed" - - "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" - "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/tasks" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" - stripmd "github.com/writeas/go-strip-markdown" -) - -//go:embed doc.md -var long string - -func NewCmdRunTasks(t *terminal.Terminal, store RunTasksStore) *cobra.Command { - var detached bool - // would be nice to have a way to pass in a list of tasks to run instead of the default - var runRemoteCMD bool - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "run-tasks", - DisableFlagsInUseLine: true, - Short: "Run background tasks for brev", - Long: stripmd.Strip(long), - Example: "brev run-tasks -d", - Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), - RunE: func(cmd *cobra.Command, args []string) error { - err := RunTasks(t, store, detached) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().BoolVarP(&detached, "detached", "d", false, "run the command in the background instead of blocking the shell") - cmd.Flags().BoolVarP(&runRemoteCMD, "run-remote-cmd", "r", true, "run the command on the instance to cd into ws default dir") - return cmd -} - -type RunTasksStore interface { - ssh.ConfigUpdaterStore - ssh.SSHConfigurerV2Store - tasks.RunTaskAsDaemonStore - GetCurrentUser() (*entity.User, error) - GetCurrentUserKeys() (*entity.UserKeys, error) -} - -func RunTasks(_ *terminal.Terminal, store RunTasksStore, detached bool) error { - ts, err := getDefaultTasks(store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if detached { - err := tasks.RunTaskAsDaemon(ts, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else { - err := tasks.RunTasks(ts) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -func getDefaultTasks(store RunTasksStore) ([]tasks.Task, error) { - configs, err := ssh.GetSSHConfigs(store) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // get private key and set here - keys, err := store.GetCurrentUserKeys() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - cu := ssh.NewConfigUpdater(store, configs, keys.PrivateKey) - - return []tasks.Task{cu}, nil -} diff --git a/pkg/cmd/runtasks/runtasks_test.go b/pkg/cmd/runtasks/runtasks_test.go deleted file mode 100644 index 494a3765..00000000 --- a/pkg/cmd/runtasks/runtasks_test.go +++ /dev/null @@ -1 +0,0 @@ -package runtasks diff --git a/pkg/cmd/secret/secret.go b/pkg/cmd/secret/secret.go deleted file mode 100644 index 11357242..00000000 --- a/pkg/cmd/secret/secret.go +++ /dev/null @@ -1,204 +0,0 @@ -// Package secret lets you add secrests. Language Go makes you write extra. -package secret - -import ( - "encoding/json" - "fmt" - - "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/store" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type SecretStore interface { - CreateSecret(req store.CreateSecretRequest) (*store.CreateSecretRequest, error) - GetCurrentUser() (*entity.User, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) -} - -func NewCmdSecret(secretStore SecretStore, t *terminal.Terminal) *cobra.Command { - var envtype string - var name string - var value string - var path string - var scope string - - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "secret", - Short: "Add a secret/environment variable", - Long: "Add a secret/environment variable to your instance, all instances in an org, or all of your instances", - Example: ` - brev secret --name my_value --value my_value --type [file, variable] --file-path --scope [org, user] - brev secret --name SERVER_URL --value https://brev.sh --type variable --scope [org, user] - brev secret --name AWS_KEY --value ... --type file --file-path --scope [org, user] - `, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - // Args: cobra.MinimumNArgs(0), - // ValidArgs: []string{"orgs", "workspaces"}, - RunE: func(cmd *cobra.Command, args []string) error { - err := addSecret(secretStore, t, envtype, name, value, path, scope) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().StringVarP(&envtype, "type", "t", "", "type of secret (env var or file)") - cmd.Flags().StringVarP(&name, "name", "n", "", "name of environment variable or secret file") - cmd.Flags().StringVarP(&value, "value", "v", "", "value of environment variable or secret file") - cmd.Flags().StringVarP(&path, "file-path", "p", "", "file path (if secret file)") - cmd.Flags().StringVarP(&scope, "scope", "s", "", "scope for env var (org or private)") - - err := cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{"file", "variable"}, cobra.ShellCompDirectiveNoSpace - }) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - err = cmd.RegisterFlagCompletionFunc("scope", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{"org", "private"}, cobra.ShellCompDirectiveNoSpace - }) - if err != nil { - breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) - fmt.Print(breverrors.WrapAndTrace(err)) - } - - return cmd -} - -func addSecret(secretStore SecretStore, t *terminal.Terminal, envtype string, name string, value string, path string, scope string) error { //nolint:funlen, gocyclo // todo simplify me - if name == "" || envtype == "" || value == "" || path == "" { - t.Vprintf(t.Yellow("\nSome flags omitted, running interactive mode!\n")) - } - - if name == "" { - name = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Environment variable/secret name: ", - ErrorMsg: "error", - }) - } - - if envtype == "" { - envtype = terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Type of variable: ", - ErrorMsg: "error", - Items: []string{"file", "variable"}, - }) - } - - if value == "" { - value = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Environment variable/secret value: ", - ErrorMsg: "error", - }) - } - - if path == "" && envtype == "file" { - path = terminal.PromptGetInput(terminal.PromptContent{ - Label: "Path for the file: ", - ErrorMsg: "error", - Default: "/home/brev/workspace/secret.txt", - }) - } - - if scope == "" { - scope = terminal.PromptSelectInput(terminal.PromptSelectContent{ - Label: "Scope: ", - ErrorMsg: "error", - Items: []string{"org", "user"}, - }) - } - - if envtype == "file" { - t.Vprintf("brev secret --name %s --value %s --type %s --file-path %s --scope %s\n", name, value, envtype, path, scope) - } else { - t.Vprintf("brev secret --name %s --value %s --type %s --scope %s\n", name, value, envtype, scope) - } - - s := t.NewSpinner() - s.Suffix = " encrypting and saving secret var" - s.Start() - - iScope := store.Org - var hierarchyID string - if scope == "user" { - iScope = store.User - // get user id - me, err := secretStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - hierarchyID = me.ID - } else { - // get org id - defaultOrg, err := secretStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - if defaultOrg == nil { - return fmt.Errorf("no orgs exist") - } - hierarchyID = defaultOrg.ID - } - - var configDest store.DestConfig - iType := store.File - if envtype == "variable" { - iType = store.EnvVariable - configDest = store.DestConfig{ - Name: name, - } - } else { - configDest = store.DestConfig{ - Path: path, - } - } - - // NOTE: hieararchyID needs to be the org ID user ID - - b := store.CreateSecretRequest{ - Name: name, - HierarchyType: iScope, - HierarchyID: hierarchyID, - Src: store.SecretReqSrc{ - Type: store.KeyValue, - Config: store.SrcConfig{ - Value: value, - }, - }, - Dest: store.SecretReqDest{ - Type: iType, - Config: configDest, - }, - } - asstring, _ := json.MarshalIndent(b, "", "\t") - fmt.Print(string(asstring)) - secret, err := secretStore.CreateSecret(b) - if err != nil { - s.Stop() - t.Vprintf(t.Red(err.Error())) - return breverrors.WrapAndTrace(err) - } - t.Vprintf(secret.Name) - s.Suffix = " environment secret added" - s.Stop() - - t.Vprintf(t.Green("\nEnvironment %s added\n", iType) + t.Yellow("\tNote: It might take up to 2 minutes to load into your environment.")) - - return nil -} diff --git a/pkg/cmd/secret/secret_test.go b/pkg/cmd/secret/secret_test.go deleted file mode 100644 index a48db44e..00000000 --- a/pkg/cmd/secret/secret_test.go +++ /dev/null @@ -1 +0,0 @@ -package secret diff --git a/pkg/cmd/shell/shell.go b/pkg/cmd/shell/shell.go index c481f378..4688c000 100644 --- a/pkg/cmd/shell/shell.go +++ b/pkg/cmd/shell/shell.go @@ -11,7 +11,6 @@ import ( "github.com/brevdev/brev-cli/pkg/analytics" "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/cmd/hello" "github.com/brevdev/brev-cli/pkg/cmd/refresh" "github.com/brevdev/brev-cli/pkg/cmd/util" "github.com/brevdev/brev-cli/pkg/entity" @@ -171,12 +170,7 @@ func runSSH(_ *entity.Workspace, sshAlias, _ string) error { sshCmd.Stdout = os.Stdout sshCmd.Stdin = os.Stdin - err := hello.SetHasRunShell(true) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = sshCmd.Run() + err := sshCmd.Run() if err != nil { return breverrors.WrapAndTrace(err) } diff --git a/pkg/cmd/test/test.go b/pkg/cmd/test/test.go deleted file mode 100644 index b0626886..00000000 --- a/pkg/cmd/test/test.go +++ /dev/null @@ -1,76 +0,0 @@ -package test - -import ( - "fmt" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - "github.com/brevdev/brev-cli/pkg/cmd/completions" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/brev-cli/pkg/util" - - "github.com/spf13/cobra" -) - -var ( - startLong = "[internal] test" - startExample = "[internal] test" -) - -type TestStore interface { - completions.CompletionStore - ResetWorkspace(workspaceID string) (*entity.Workspace, error) - GetAllWorkspaces(options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetWorkspace(id string) (*entity.Workspace, error) - GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) - CopyBin(targetBin string) error - GetSetupScriptContentsByURL(url string) (string, error) - UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) -} - -type ServiceMeshStore interface { - autostartconf.AutoStartStore - GetWorkspace(workspaceID string) (*entity.Workspace, error) -} - -func NewCmdTest(_ *terminal.Terminal, _ TestStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"devonly": ""}, - Use: "test", - DisableFlagsInUseLine: true, - Short: "[internal] Test random stuff.", - Long: startLong, - Example: startExample, - // Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), - RunE: func(cmd *cobra.Command, args []string) error { - // fmt.Printf("NAME ID URL SOMETHING ELSE") - // hello.TypeItToMe("\n\n\n") - // hello.TypeItToMe("👆 this is the name of your environment (which you can use to open the environment)") - // time.Sleep(1 * time.Second) - // fmt.Printf("\332K\r") - // fmt.Println(" ") - // hello.TypeItToMe(" 👆 you can expose your localhost to this public URL") - // time.Sleep(1 * time.Second) - // fmt.Printf("\332K\r") - // fmt.Printf("bye world") - // fmt.Printf("bye world") - - // s := t.Yellow("\n\nCould you please install the following VSCode extension? %s", t.Green("ms-vscode-remote.remote-ssh")) - // s += "\nDo that then run " + t.Yellow("brev hello") + " to resume this walk-through\n" - // // s += "Here's a video of me installing the VS Code extension 👉 " + "" - // hello.TypeItToMe(s) - - res := util.DoesPathExist("/Users/naderkhalil/brev-cli") - // res := util.DoesPathExist("/home/brev/workspace") - fmt.Println(res) - - return nil - }, - } - - return cmd -} diff --git a/pkg/cmd/test/test_test.go b/pkg/cmd/test/test_test.go deleted file mode 100644 index 56e54040..00000000 --- a/pkg/cmd/test/test_test.go +++ /dev/null @@ -1 +0,0 @@ -package test diff --git a/pkg/cmd/updatemodel/updatemodel.go b/pkg/cmd/updatemodel/updatemodel.go deleted file mode 100644 index 032563f2..00000000 --- a/pkg/cmd/updatemodel/updatemodel.go +++ /dev/null @@ -1,422 +0,0 @@ -package updatemodel - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/hashicorp/go-multierror" - "github.com/samber/lo" - "github.com/spf13/cobra" - - "github.com/brevdev/brev-cli/pkg/autostartconf" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/brevdev/parse/pkg/parse" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type updatemodelStore interface { - ModifyWorkspace(workspaceID string, options *store.ModifyWorkspaceRequest) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - WriteString(path, data string) error - UserHomeDir() (string, error) - ListDirs(path string) ([]string, error) - FileExists(filepath string) (bool, error) - GetEnvSetupParams(wsid string) (*store.SetupParamsV0, error) - GetCurrentUser() (*entity.User, error) -} - -func NewCmdupdatemodel(t *terminal.Terminal, store updatemodelStore) *cobra.Command { - var configure bool - cmd := &cobra.Command{ - Use: "updatemodel", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: updateModel{ - t: t, - Store: store, - clone: func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) { - workspaceID, err := store.GetCurrentWorkspaceID() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - params, err := store.GetEnvSetupParams(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - if params != nil && params.WorkspaceKeyPair != nil { - keys := params.WorkspaceKeyPair - pubkeys, err := ssh.NewPublicKeys("ubuntu", []byte(keys.PrivateKeyData), "") //nolint:govet //abc - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - o.Auth = pubkeys - } - - r, err := git.PlainClone(path, isBare, o) - return r, breverrors.WrapAndTrace(err) - }, - open: func(path string) (repo, error) { - r, err := git.PlainOpen(path) - return r, breverrors.WrapAndTrace(err) - }, - configure: configure, - }.RunE, - } - - cmd.Flags().BoolVarP(&configure, "configure", "c", false, "configure daemon") - return cmd -} - -type repo interface { - Remotes() ([]*git.Remote, error) -} - -type updateModel struct { - t *terminal.Terminal - Store updatemodelStore - clone func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) - open func(path string) (repo, error) - configure bool -} - -func (u updateModel) RunE(_ *cobra.Command, _ []string) error { //nolint:funlen //abc - if u.configure { - return breverrors.WrapAndTrace( - DaemonConfigurer{ - Store: u.Store, - }.Configure(), - ) - } - - remotes, err := u.remotes() - if err != nil { - return breverrors.WrapAndTrace(err) - } - workspaceID, err := u.Store.GetCurrentWorkspaceID() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - workspace, err := u.Store.GetWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // there should only be one but sometimes there are more and thats not - // handled right now - urls := lo.FlatMap( - remotes, - func(remote *git.Remote, _ int) []string { - return remote.Config().URLs - }, - ) - - // filter user dotbrev remote url - user, err := u.Store.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - dotbrev := user.BaseWorkspaceRepo - urls = lo.Filter( - urls, - func(url string, _ int) bool { - return !parse.CMP(url, dotbrev) - }, - ) - - reposv1FromENV := makeReposFromRemotes(urls) - - // merge reposv0 and gitrepo field into reposv1fromBE - - reposv1FromBE := make(entity.ReposV1) - reposv1FromBE[entity.RepoName(parse.GetRepoNameFromOrigin(workspace.GitRepo))] = entity.RepoV1{ - GitRepo: entity.GitRepo{ - Repository: workspace.GitRepo, - }, - Type: entity.GitRepoType, - } - - for key, val := range workspace.ReposV0 { - reposv1FromBE[key] = entity.RepoV1{ - GitRepo: entity.GitRepo{ - Repository: val.Repository, - }, - Type: entity.GitRepoType, - } - } - if workspace.ReposV1 != nil { - for key, val := range *workspace.ReposV1 { - key := key - val := val - reposv1FromBE[key] = val - } - } - - rm := &repoMerger{ - acc: &reposv1FromBE, - repos: []*entity.ReposV1{reposv1FromENV}, - } - _, err = u.Store.ModifyWorkspace( - workspaceID, - &store.ModifyWorkspaceRequest{ - ReposV1: rm.MergeBE(), - }, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - dir, err := u.Store.UserHomeDir() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - cloneErrors := lo.Map( - rm.ReposToClone(), - func(repo *entity.RepoV1, _ int) error { - _, err := u.clone(dir, false, &git.CloneOptions{ - URL: repo.GitRepo.Repository, - }) - return breverrors.WrapAndTrace(err) - }, - ) - return breverrors.WrapAndTrace( - lo.Reduce( - cloneErrors, - func(acc error, err error, _ int) error { - if err != nil { - txt := fmt.Sprintf("%s", err) - if strings.Contains(txt, "already exists") { - return acc - } - if acc == nil { - return breverrors.WrapAndTrace(err) - } - return multierror.Append(acc, err) - } - if acc == nil { - return breverrors.WrapAndTrace(err) - } - return acc - }, - nil, - ), - ) -} - -type stringWriter interface { - WriteString(path, data string) error -} - -type DaemonConfigurer struct { - Store stringWriter -} - -func (dc DaemonConfigurer) Configure() error { - // create systemd service file to run - // brev updatemodel -d /home/ubuntu - configFile := filepath.Join("/etc/systemd/system", "brev-updatemodel.service") - err := dc.Store.WriteString( - configFile, - `[Unit] -Description=Brev Update Model -After=network.target - -[Service] -Type=simple -User=ubuntu -ExecStart=/usr/bin/brev updatemodel -d /home/ubuntu -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if autostartconf.ShouldSymlink() { - symlinkTarget := path.Join("/etc/systemd/system/default.target.wants/", "brev-updatemodel.service") - err2 := os.Symlink(configFile, symlinkTarget) - if err2 != nil { - return breverrors.WrapAndTrace(err2) - } - } - // create systemd timer to run every 5 seconds - err = dc.Store.WriteString( - "/etc/systemd/system/brev-updatemodel.timer", - `[Unit] -Description=Brev Update Model Timer - -[Timer] -OnBootSec=5 -OnUnitActiveSec=5 - -[Install] -WantedBy=timers.target -`) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - // enable timer - err = autostartconf.ExecCommands( - [][]string{ - {"systemctl", "enable", "brev-updatemodel.timer"}, - {"systemctl", "start", "brev-updatemodel.timer"}, - {"systemctl", "daemon-reload"}, - }, - ) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func makeReposFromRemotes(remotes []string) *entity.ReposV1 { - return lo.Reduce( - remotes, - func(acc *entity.ReposV1, remote string, _ int) *entity.ReposV1 { - name := parse.GetRepoNameFromOrigin(remote) - url := parse.GetSSHURLFromOrigin(remote) - a := *acc - a[entity.RepoName(name)] = entity.RepoV1{ - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: url, - }, - } - return &a - }, - &entity.ReposV1{}, - ) -} - -type repoMerger struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 -} - -func (r *repoMerger) MergeBE() *entity.ReposV1 { - for _, repo := range r.repos { - for k, v := range *repo { - if _, ok := (*r.acc)[k]; ok { - continue - } - _, valueInAcc := lo.Find( - r.accValues(), - func(repo *entity.RepoV1) bool { - return repo.GitRepo.Repository == v.GitRepo.Repository - }, - ) - if valueInAcc { - continue - } - (*r.acc)[k] = v - } - } - return r.acc -} - -func (r *repoMerger) ReposToClone() []*entity.RepoV1 { - // repos present in the BE but not in the ENV - return lo.Filter( - r.accValues(), - func(accrepo *entity.RepoV1, _ int) bool { - _, valueInENV := lo.Find( - r.reposValues(), - func(repo *entity.RepoV1) bool { - return accrepo.GitRepo.Repository == repo.GitRepo.Repository - }, - ) - return !valueInENV - }, - ) -} - -func (r repoMerger) reposValues() []*entity.RepoV1 { - values := []*entity.RepoV1{} - for _, repo := range r.repos { - for _, v := range *repo { - // explicit memory aliasing in for loop. - v := v - values = append(values, &v) - } - } - return values -} - -func (r repoMerger) accValues() []*entity.RepoV1 { - if r.acc == nil { - return []*entity.RepoV1{} - } - values := []*entity.RepoV1{} - for _, v := range *r.acc { - newV := v - values = append(values, &newV) - } - return values -} - -func (u updateModel) remotes() ([]*git.Remote, error) { - dir, err := u.Store.UserHomeDir() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - dirs, err := u.Store.ListDirs(dir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - // filter dirs that start with . - dirs = lo.Filter( - dirs, - func(dir string, _ int) bool { - return !strings.HasPrefix(dir, ".") - }, - ) - - dirsWithGit := []string{} - for _, dir := range dirs { - gitDir := path.Join(dir, ".git") - exists, err := u.Store.FileExists(gitDir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - if exists { - dirsWithGit = append(dirsWithGit, dir) - } - } - - repos := []*git.Repository{} - for _, dir := range dirsWithGit { - repo, err := git.PlainOpen(dir) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - repos = append(repos, repo) - } - - remotes := []*git.Remote{} - for _, repo := range repos { - repoRemotes, err := repo.Remotes() - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - remotes = append(remotes, repoRemotes...) - } - return remotes, nil -} diff --git a/pkg/cmd/updatemodel/updatemodel_test.go b/pkg/cmd/updatemodel/updatemodel_test.go deleted file mode 100644 index 7a13c50f..00000000 --- a/pkg/cmd/updatemodel/updatemodel_test.go +++ /dev/null @@ -1,292 +0,0 @@ -package updatemodel - -import ( - "testing" - - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/go-git/go-git/v5" - "github.com/google/go-cmp/cmp" - "github.com/spf13/cobra" -) - -type updatemodelStoreMock struct{} - -func (u updatemodelStoreMock) ModifyWorkspace(_ string, _ *store.ModifyWorkspaceRequest) (*entity.Workspace, error) { - return nil, nil -} - -func (u updatemodelStoreMock) GetCurrentWorkspaceID() (string, error) { - return "test", nil -} - -func (u updatemodelStoreMock) GetWorkspace(_ string) (*entity.Workspace, error) { - reposv1 := entity.ReposV1{ - entity.RepoName("test"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "foo", - }, - }, - } - return &entity.Workspace{ - ReposV1: &reposv1, - }, nil -} - -func (u updatemodelStoreMock) WriteString(_, _ string) error { - return nil -} - -func (u updatemodelStoreMock) UserHomeDir() (string, error) { - return "", nil -} - -func (u updatemodelStoreMock) ListDirs(_ string) ([]string, error) { - return nil, nil -} - -func (u updatemodelStoreMock) FileExists(_ string) (bool, error) { - return false, nil -} - -func (u updatemodelStoreMock) GetEnvSetupParams(_ string) (*store.SetupParamsV0, error) { - return nil, nil -} - -func (u updatemodelStoreMock) GetCurrentUser() (*entity.User, error) { - return nil, nil -} - -func mockPlainClone(_ string, _ bool, _ *git.CloneOptions) (*git.Repository, error) { - return nil, nil -} - -type remotes struct{} - -func (r remotes) Remotes() ([]*git.Remote, error) { - return nil, nil -} - -func mockPlainOpen(_ string) (repo, error) { - return remotes{}, nil -} - -func TestUpdateModel_RunE(t *testing.T) { - type fields struct { - t *terminal.Terminal - Store updatemodelStore - // directory string - clone func(path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) - open func(path string) (repo, error) - configure bool - } - type args struct { - in0 *cobra.Command - in1 []string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - // TODO: Add test cases. - // TODO: Add test cases. - { - name: "test", - fields: fields{ - t: nil, - Store: updatemodelStoreMock{}, - clone: mockPlainClone, - open: mockPlainOpen, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u := updateModel{ - t: tt.fields.t, - Store: tt.fields.Store, - clone: tt.fields.clone, - open: tt.fields.open, - configure: tt.fields.configure, - } - t.Skip("TODO: fix this test") - if err := u.RunE(tt.args.in0, tt.args.in1); (err != nil) != tt.wantErr { - t.Errorf("UpdateModel.RunE() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_makeReposFromRemotes(t *testing.T) { - type args struct { - remotes []string - } - tests := []struct { - name string - args args - want *entity.ReposV1 - }{ - // TODO: Add test cases. - { - name: "test", - args: args{ - remotes: []string{"git@github.com:brevdev/brev-cli.git", "git@github.com:brevdev/brev-deploy.git"}, - }, - want: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com:brevdev/brev-cli.git", - }, - }, - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com:brevdev/brev-deploy.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeReposFromRemotes(tt.args.remotes) - // cmp.Diff is used to compare the two structs - if diff := cmp.Diff(got, tt.want); diff != "" { - t.Errorf("makeReposFromRemotes() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_repoMerger_MergeBE(t *testing.T) { - type fields struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 - } - tests := []struct { - name string - fields fields - want *entity.ReposV1 - }{ - // TODO: Add test cases. - { - name: "test", - fields: fields{ - acc: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - repos: []*entity.ReposV1{ - { - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - { - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-cli.git", - }, - }, - }, - }, - }, - want: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &repoMerger{ - acc: tt.fields.acc, - repos: tt.fields.repos, - } - // using cmp - if diff := cmp.Diff(r.MergeBE(), tt.want); diff != "" { - t.Errorf("repoMerger.MergeBE() mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func Test_repoMerger_ReposToClone(t *testing.T) { - type fields struct { - acc *entity.ReposV1 - repos []*entity.ReposV1 - } - tests := []struct { - name string - fields fields - want []*entity.RepoV1 - }{ - // TODO: Add test cases. - { - name: "test", - fields: fields{ - acc: &entity.ReposV1{ - entity.RepoName("brev-cli"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - repos: []*entity.ReposV1{ - { - entity.RepoName("brev-deploy"): { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "github.com/brevdev/brev-deploy.git", - }, - }, - }, - }, - }, - want: []*entity.RepoV1{ - { - Type: entity.GitRepoType, - GitRepo: entity.GitRepo{ - Repository: "git@github.com/brevdev/brev-cli.git", - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &repoMerger{ - acc: tt.fields.acc, - repos: tt.fields.repos, - } - if diff := cmp.Diff(r.ReposToClone(), tt.want); diff != "" { - t.Errorf("repoMerger.ReposToClone() mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/cmd/writeconnectionevent/writeconnectionevent.go b/pkg/cmd/writeconnectionevent/writeconnectionevent.go deleted file mode 100644 index e7440ccf..00000000 --- a/pkg/cmd/writeconnectionevent/writeconnectionevent.go +++ /dev/null @@ -1,44 +0,0 @@ -package writeconnectionevent - -import ( - "github.com/spf13/cobra" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" -) - -var ( - short = "TODO" - long = "TODO" - example = "TODO" -) - -type writeConnectionEventStore interface { - WriteConnectionEvent() error -} - -func NewCmdwriteConnectionEvent(t *terminal.Terminal, store writeConnectionEventStore) *cobra.Command { - cmd := &cobra.Command{ - Use: "write-connection-event", - DisableFlagsInUseLine: true, - Short: short, - Long: long, - Example: example, - RunE: func(cmd *cobra.Command, args []string) error { - err := RunWriteConnectionEvent(t, args, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - return cmd -} - -func RunWriteConnectionEvent(_ *terminal.Terminal, _ []string, store writeConnectionEventStore) error { - err := store.WriteConnectionEvent() - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} diff --git a/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go b/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go deleted file mode 100644 index 4d2784e3..00000000 --- a/pkg/cmd/writeconnectionevent/writeconnectionevent_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package writeconnectionevent - -import ( - "testing" - - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/afero" -) - -func TestRunWriteConnectionEvent(t *testing.T) { - fs := afero.NewMemMapFs() - type args struct { - in0 *terminal.Terminal - in1 []string - store writeConnectionEventStore - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - { - name: "write connection event", - args: args{ - nil, - []string{}, - store.NewBasicStore().WithFileSystem(fs), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := RunWriteConnectionEvent(tt.args.in0, tt.args.in1, tt.args.store); (err != nil) != tt.wantErr { - t.Errorf("RunWriteConnectionEvent() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} From 3ba4342e17479381584673b1da71be61214930ca Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 01:03:14 +0000 Subject: [PATCH 2/7] Fix lint error: remove unused nolint directive The funlen nolint directive is no longer needed after removing deprecated commands from createCmdTree function. Also fix gofumpt formatting in notebook.go. Co-Authored-By: Alec Fong --- pkg/cmd/cmd.go | 2 +- pkg/cmd/notebook/notebook.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 67f8e48e..f75438f7 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -209,7 +209,7 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin return cmds } -func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore, loginAuth *auth.LoginAuth) { //nolint:funlen // define brev command +func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore, loginAuth *auth.LoginAuth) { cmd.AddCommand(set.NewCmdSet(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(ls.NewCmdLs(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(org.NewCmdOrg(t, loginCmdStore, noLoginCmdStore)) diff --git a/pkg/cmd/notebook/notebook.go b/pkg/cmd/notebook/notebook.go index 0ab871fe..186d5ef3 100644 --- a/pkg/cmd/notebook/notebook.go +++ b/pkg/cmd/notebook/notebook.go @@ -2,7 +2,7 @@ package notebook import ( "fmt" - + "github.com/brevdev/brev-cli/pkg/cmd/portforward" "github.com/brevdev/brev-cli/pkg/cmd/util" "github.com/brevdev/brev-cli/pkg/entity" From b648008e2e9a675f076e8704f1dc516d283cb3ec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:57:15 +0000 Subject: [PATCH 3/7] Remove ollama command - Remove ollama import and registration from cmd.go - Delete pkg/cmd/ollama directory and all related files - Ollama command was identified as an additional removal request This completes the CLI command cleanup by removing the AI/ML model server functionality from the core CLI. Co-Authored-By: Alec Fong --- pkg/cmd/cmd.go | 2 - pkg/cmd/ollama/ollama.go | 343 ------------------------------- pkg/cmd/ollama/ollama_test.go | 1 - pkg/cmd/ollama/ollamauiverb.yaml | 0 pkg/cmd/ollama/ollamaverb.yaml | 18 -- 5 files changed, 364 deletions(-) delete mode 100644 pkg/cmd/ollama/ollama.go delete mode 100644 pkg/cmd/ollama/ollama_test.go delete mode 100644 pkg/cmd/ollama/ollamauiverb.yaml delete mode 100644 pkg/cmd/ollama/ollamaverb.yaml diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index f75438f7..9d94774e 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -16,7 +16,6 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/logout" "github.com/brevdev/brev-cli/pkg/cmd/ls" "github.com/brevdev/brev-cli/pkg/cmd/notebook" - "github.com/brevdev/brev-cli/pkg/cmd/ollama" "github.com/brevdev/brev-cli/pkg/cmd/open" "github.com/brevdev/brev-cli/pkg/cmd/org" "github.com/brevdev/brev-cli/pkg/cmd/portforward" @@ -233,7 +232,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(shell.NewCmdShell(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(copy.NewCmdCopy(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(open.NewCmdOpen(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(ollama.NewCmdOllama(t, loginCmdStore)) cmd.AddCommand(status.NewCmdStatus(t, loginCmdStore)) cmd.AddCommand(sshkeys.NewCmdSSHKeys(t, loginCmdStore)) cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) diff --git a/pkg/cmd/ollama/ollama.go b/pkg/cmd/ollama/ollama.go deleted file mode 100644 index c1c2585c..00000000 --- a/pkg/cmd/ollama/ollama.go +++ /dev/null @@ -1,343 +0,0 @@ -// Package ollama is for starting AI/ML model workspaces -package ollama - -import ( - _ "embed" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/google/uuid" - - "github.com/brevdev/brev-cli/pkg/cmd/refresh" - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/config" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/instancetypes" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -var ( - ollamaLong = "Start an Ollama server with specified model types" - ollamaExample = ` - brev ollama --model llama3 - ` -) - -//go:embed ollamaverb.yaml -var verbYaml string - -type OllamaStore interface { - refresh.RefreshStore - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - BuildVerbContainer(workspaceID string, verbYaml string) (*store.BuildVerbRes, error) - ModifyPublicity(workspace *entity.Workspace, applicationName string, publicity bool) (*entity.Tunnel, error) -} - -func validateModelType(input string) (bool, error) { - var model string - var tag string - - split := strings.Split(input, ":") - switch len(split) { - case 2: - model = split[0] - tag = split[1] - case 1: - model = input - tag = "latest" - default: - return false, fmt.Errorf("invalid model type: %s", input) - } - valid, err := store.ValidateOllamaModel(model, tag) - if err != nil { - return false, fmt.Errorf("error validating model: %s", err) - } - return valid, nil -} - -func NewCmdOllama(t *terminal.Terminal, ollamaStore OllamaStore) *cobra.Command { - var model string - var gpu string - - cmd := &cobra.Command{ - Use: "ollama", - DisableFlagsInUseLine: true, - Short: "Start an Ollama model server", - Long: ollamaLong, - Example: ollamaExample, - Annotations: map[string]string{ - "quickstart": "", - }, - RunE: func(cmd *cobra.Command, args []string) error { - if model == "" { - return fmt.Errorf("model type must be specified") - } - - isValid, valErr := validateModelType(model) - if valErr != nil { - return valErr - } - if !isValid { - return fmt.Errorf("invalid model type: %s", model) - } - if gpu != "" { - isValid := instancetypes.ValidateInstanceType(gpu) - if !isValid { - err := fmt.Errorf("invalid GPU instance type: %s, see https://brev.dev/docs/reference/gpu for a list of valid GPU instance types", gpu) - return breverrors.WrapAndTrace(err) - } - } - - // Start the network call in a goroutine - res := collections.Async(func() (any, error) { - err := runOllamaWorkspace(t, RunOptions{ - Model: model, - GPUType: gpu, - }, ollamaStore) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - return nil, nil - }) - - // Type out the creating workspace message - t.Vprint("🤙🦙🤙🦙🤙🦙🤙") - t.Vprint(t.Green("\n\n\n")) - - _, err := res.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - } - cmd.Flags().StringVarP(&model, "model", "m", "", "AI/ML model type (e.g., llama2, llama3, mistral7b)") - cmd.Flags().StringVarP(&gpu, "gpu", "g", "g5.xlarge", "GPU instance type. See https://brev.dev/docs/reference/gpu for details") - return cmd -} - -type RunOptions struct { - Model string - GPUType string -} - -func runOllamaWorkspace(t *terminal.Terminal, opts RunOptions, ollamaStore OllamaStore) error { //nolint:funlen, gocyclo // todo - _, err := ollamaStore.GetCurrentUser() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - org, err := ollamaStore.GetActiveOrganizationOrDefault() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - instanceType := opts.GPUType - clusterID := config.GlobalConfig.GetDefaultClusterID() - uuid := uuid.New().String() - instanceName := fmt.Sprintf("ollama-%s", uuid) - cwOptions := store.NewCreateWorkspacesOptions(clusterID, instanceName).WithInstanceType(instanceType) - - t.Vprintf("Creating Ollama server %s with model %s in org %s\n", t.Green(cwOptions.Name), t.Green(opts.Model), t.Green(org.ID)) - - s := t.NewSpinner() - - createWorkspaceRes := collections.Async(func() (*entity.Workspace, error) { - w, errr := ollamaStore.CreateWorkspace(org.ID, cwOptions) - if errr != nil { - return nil, breverrors.WrapAndTrace(errr) - } - return w, nil - }) - - s.Suffix = " Creating your workspace. Hang tight 🤙" - s.Start() - - w, err := createWorkspaceRes.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var vmStatus bool - vmStatus, err = pollInstanceUntilVMReady(w, time.Second, time.Minute*5, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !vmStatus { - return breverrors.New("instance did not start") - } - s.Stop() - t.Vprint("VM is ready!\n") - s.Start() - - // TODO: look into timing of verb call - time.Sleep(time.Second * 5) - - verbBuildRes := collections.Async(func() (*store.BuildVerbRes, error) { - lf, errr := ollamaStore.BuildVerbContainer(w.ID, verbYaml) - if errr != nil { - return nil, breverrors.WrapAndTrace(errr) - } - return lf, nil - }) - - s.Suffix = " Building the Ollama container. Hang tight 🤙" - - _, err = verbBuildRes.Await() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - var vstatus bool - // TODO: 15 min for now because the image is not cached and takes a while to build. Remove this when the image is cached - vstatus, err = pollInstanceUntilVerbContainerReady(w, time.Second, time.Minute*20, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if !vstatus { - return breverrors.New("verb container did not build correctly") - } - s.Stop() - - s = t.NewSpinner() - s.Suffix = "(connectivity) Pulling the %s model, just a bit more! 🏄" - - // shell in and run ollama pull: - if refreshErr := refresh.RunRefresh(ollamaStore); refreshErr != nil { - return breverrors.WrapAndTrace(refreshErr) - } - // Reload workspace to get the latest status - w, err = ollamaStore.GetWorkspace(w.ID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - link, name, err := getOllamaTunnelLink(w) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - _, err = makeTunnelPublic(w, name, ollamaStore) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - s.Suffix = "Pulling the %s model, just a bit more! 🏄" - - // shell in and run ollama pull: - if err := runSSHExec(instanceName, []string{"ollama", "pull", opts.Model}, false); err != nil { - return breverrors.WrapAndTrace(err) - } - if err := runSSHExec(instanceName, []string{"ollama", "run", opts.Model, "hello world"}, true); err != nil { - return breverrors.WrapAndTrace(err) - } - s.Stop() - - fmt.Print("\n") - t.Vprint(t.Green("Ollama is ready to go!\n")) - displayOllamaConnectBreadCrumb(t, link, opts.Model) - return nil -} - -func displayOllamaConnectBreadCrumb(t *terminal.Terminal, link string, model string) { - t.Vprintf(t.Green("Query the Ollama API with the following command:\n")) - t.Vprintf(t.Yellow(fmt.Sprintf("curl %s/api/chat -d '{\n \"model\": \"%s\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"why is the sky blue?\"\n }\n ]\n}'\n", link, model))) -} - -func pollInstanceUntilVMReady(workspace *entity.Workspace, interval time.Duration, timeout time.Duration, ollamaStore OllamaStore) (bool, error) { - elapsedTime := time.Duration(0) - - for elapsedTime < timeout { - w, err := ollamaStore.GetWorkspace(workspace.ID) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } else if w.Status == "RUNNING" { - // adding a slight delay to make sure the instance is ready - time.Sleep(time.Minute * 1) - return true, nil - } - time.Sleep(interval) - elapsedTime += interval - } - return false, breverrors.New("Timeout waiting for machine to start") -} - -func pollInstanceUntilVerbContainerReady(workspace *entity.Workspace, interval time.Duration, timeout time.Duration, ollamaStore OllamaStore) (bool, error) { - elapsedTime := time.Duration(0) - - for elapsedTime < timeout { - w, err := ollamaStore.GetWorkspace(workspace.ID) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } else if w.VerbBuildStatus == entity.Completed { - return true, nil - } - time.Sleep(interval) - elapsedTime += interval - } - return false, breverrors.New("Timeout waiting for container to build") -} - -func getOllamaTunnelLink(workspace *entity.Workspace) (string, string, error) { - for _, v := range workspace.Tunnel.Applications { - if v.Port == 11434 { - return v.Hostname, v.Name, nil - } - } - return "", "", breverrors.New("Could not find Ollama tunnel") -} - -// TODO: stub for granular permissioning -// func generateCloudflareAPIKeys(workspace *entity.Workspace, ollamaStore OllamaStore) (bool, error) { -// return false, nil -// } - -func makeTunnelPublic(workspace *entity.Workspace, applicationName string, ollamaStore OllamaStore) (bool, error) { - t, err := ollamaStore.ModifyPublicity(workspace, applicationName, true) - if err != nil { - return false, breverrors.WrapAndTrace(err) - } - for _, v := range t.Applications { - if v.Port == 11434 { - if v.Policy.AllowEveryone { - return true, nil - } - } - } - return false, breverrors.New("Could not find Ollama tunnel") -} - -func runSSHExec(sshAlias string, args []string, fireAndForget bool) error { - sshAgentEval := "eval $(ssh-agent -s)" - cmd := fmt.Sprintf("ssh %s -- %s", sshAlias, strings.Join(args, " ")) - cmd = fmt.Sprintf("%s && %s", sshAgentEval, cmd) - sshCmd := exec.Command("bash", "-c", cmd) //nolint:gosec //cmd is user input - - if fireAndForget { - if err := sshCmd.Start(); err != nil { - return fmt.Errorf("error starting SSH command: %w", err) - } - return nil - } - sshCmd.Stderr = os.Stderr - sshCmd.Stdout = os.Stdout - sshCmd.Stdin = os.Stdin - if err := sshCmd.Run(); err != nil { - return fmt.Errorf("error running SSH command: %w", err) - } - return nil -} diff --git a/pkg/cmd/ollama/ollama_test.go b/pkg/cmd/ollama/ollama_test.go deleted file mode 100644 index f74dfdc0..00000000 --- a/pkg/cmd/ollama/ollama_test.go +++ /dev/null @@ -1 +0,0 @@ -package ollama diff --git a/pkg/cmd/ollama/ollamauiverb.yaml b/pkg/cmd/ollama/ollamauiverb.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/cmd/ollama/ollamaverb.yaml b/pkg/cmd/ollama/ollamaverb.yaml deleted file mode 100644 index e8b9e896..00000000 --- a/pkg/cmd/ollama/ollamaverb.yaml +++ /dev/null @@ -1,18 +0,0 @@ -build: - python_version: "3.10" - cuda: 12.0.1 - python_packages: - - jupyterlab - run: - - curl -fsSL https://ollama.com/install.sh | sh -user: - shell: zsh - authorized_keys_path: /home/ubuntu/.ssh/authorized_keys -ports: - - "2222:22" - - "8000:8000" -services: - - name: ollama-server - entrypoint: OLLAMA_HOST=0.0.0.0 ollama serve - ports: - - 127.0.0.1:11434:11434 From 8db4a51b771538f78a9113711daa2235fe032a55 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:49:57 +0000 Subject: [PATCH 4/7] Remove status command - Remove status import and registration from cmd.go - Delete pkg/cmd/status directory and all related files - Status command was identified as an additional removal request This further streamlines the CLI by removing the instance status functionality that was redundant with other status checking methods. Co-Authored-By: Alec Fong --- pkg/cmd/cmd.go | 2 - pkg/cmd/status/doc.md | 268 ---------------------------------- pkg/cmd/status/status.go | 61 -------- pkg/cmd/status/status_test.go | 1 - 4 files changed, 332 deletions(-) delete mode 100644 pkg/cmd/status/doc.md delete mode 100644 pkg/cmd/status/status.go delete mode 100644 pkg/cmd/status/status_test.go diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 9d94774e..3b87004c 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -29,7 +29,6 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/shell" "github.com/brevdev/brev-cli/pkg/cmd/sshkeys" "github.com/brevdev/brev-cli/pkg/cmd/start" - "github.com/brevdev/brev-cli/pkg/cmd/status" "github.com/brevdev/brev-cli/pkg/cmd/stop" "github.com/brevdev/brev-cli/pkg/cmd/tasks" "github.com/brevdev/brev-cli/pkg/cmd/version" @@ -232,7 +231,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(shell.NewCmdShell(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(copy.NewCmdCopy(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(open.NewCmdOpen(t, loginCmdStore, noLoginCmdStore)) - cmd.AddCommand(status.NewCmdStatus(t, loginCmdStore)) cmd.AddCommand(sshkeys.NewCmdSSHKeys(t, loginCmdStore)) cmd.AddCommand(start.NewCmdStart(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(create.NewCmdCreate(t, loginCmdStore)) diff --git a/pkg/cmd/status/doc.md b/pkg/cmd/status/doc.md deleted file mode 100644 index fb97a991..00000000 --- a/pkg/cmd/status/doc.md +++ /dev/null @@ -1,268 +0,0 @@ -create start and join a workspace - -## Synopsis - -``` - -brev start { ARG | -e} {-n | --name} {-c | --class} { -s | --setup-script} - {-r | --setup-repo} {-p | --setup-path } { -o | --org} -``` - -## Description - -brev start can do the following: - -- start a stopped workspace -- join a workspace in an organization -- create an empty workspace -- create a workspace from a directory on your computer -- create a workspace from a git url - -## Flags - -### -n --name - -specify the name for your workspace instead of brev-cli generating one for you. - -for example, to override the name of a workspace when creating a workspace from -a git repo you could do it with then `-n` flag. This example creates a repo with -the name `cli` from the git repo `https://github.com/brevdev/brev-cli`. - -``` -$ brev start https://github.com/brevdev/brev-cli -n cli -``` - -## Examples - -### Create an empty workspace - -``` -$ brev start -e -n foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -or - -``` -$ brev start --empty --name foo -``` - -which has an output similar too: - -``` -name foo -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -workspace is starting. this can take up to 2 minutes the first time. - -you can safely ctrl+c to exit -⣽ workspace is deploying -your workspace is ready! - -connect to the workspace: - brev open foo # brev open -> open workspace in preferred editor - brev shell foo # brev shell -> ssh into workspace (shortcut) - ssh foo-8j4u # ssh -> ssh directly to workspace - -``` - -view your workspace with `brev ls` - -### create a workspace, and do not block shell until workspace is created - -use the `-d` or `--detached` flag to create a workspace and immediately exit -rather than wait for workspace to be successfully created before exiting. - -``` -$ brev start -d -e -n bar -``` - -which has an output similar too: - -``` -name bar -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -Workspace is starting. This can take up to 2 minutes the first time. -``` - -### Create a workspace from a file path - -if in your current directory has a directory in it called `merge-json`, you can -create a workspace using the contents of that directory using -`brev start merge-json` - -``` -$ ls -merge-json -``` - -``` -$ brev start merge-json - -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name merge-json -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⡿ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open merge-json # brev open -> open workspace in preferred editor - brev shell merge-json # brev shell -> ssh into workspace (shortcut) - ssh merge-json-wd6q # ssh -> ssh directly to workspace -``` - -### Create a workspace from a git repository - -``` -$ brev start https://github.com/brevdev/react-starter-app -``` - -which has an output similar too: - -``` -Workspace is starting. This can take up to 2 minutes the first time. - -name react-starter-app -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣾ workspace is deploying -Your workspace is ready! - -Connect to the workspace: - brev open react-starter-app # brev open -> open workspace in preferred editor - brev shell react-starter-app # brev shell -> ssh into workspace (shortcut) - ssh react-starter-app-8v8p # ssh -> ssh directly to workspace - -``` - -### Join a workspace in your orginization - -view your orgs workspaces with `brev ls --all`. Workspaces in your org that you -have not joined appear at the bottom of the output. - -``` -$ brev ls --all -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - brev-cli RUNNING brev-cli-p09m-brevdev.wgt-us-west-2-test.brev.dev x1yxqp09m - -Connect to running workspace: - brev open brev-cli # brev open -> open workspace in preferred editor - brev shell brev-cli # brev shell -> ssh into workspace (shortcut) - ssh brev-cli-p09m # ssh -> ssh directly to workspace - -7 other projects in Org brev.dev - NAME MEMBERS - new-docs 1 - brev-landing-page 2 - todo-app 1 - vagrant-guide 1 - mern-template 1 - solidity-nextjs-starter 1 - akka-http-quickstart-scala 1 - -Join a project: - brev start new-docs - -``` - -join the project new-docs - -``` -$ brev start new-docs -``` - -which has an output similar too: - -``` -Name flag omitted, using auto generated name: new-docs -Workspace is starting. This can take up to 2 minutes the first time. - -name new-docs -template 4nbb4lg2s ubuntu -resource class 2x8 -workspace group brev-test-brevtenant-cluster -You can safely ctrl+c to exit -⣟ workspace is deploying Connect to the workspace: - brev open new-docs # brev open -> open workspace in preferred editor - brev shell new-docs # brev shell -> ssh into workspace (shortcut) - ssh new-docs-pek9 # ssh -> ssh directly to workspace -``` - -### Start a stopped workspace - -If you have already joined a workspace and have stopped it with `brev stop`, -you can start it again with `brev start` - -view your current workspaces with `brev ls` - -``` -$ brev ls -``` - -which has an output similar too: - -``` -You have 1 workspace in Org brev.dev - NAME STATUS URL ID - linear-client STOPPED linear-client-yw1a-brevdev.wgt-us-west-2-test.brev.dev gov5jyw1a - -Connect to running workspace: - brev open linear-client # brev open -> open workspace in preferred editor - brev shell linear-client # brev shell -> ssh into workspace (shortcut) - ssh linear-client-yw1a # ssh -> ssh directly to workspace - -``` - -join the workspace - -``` -$ brev start linear-client -``` - -which has an output similar too: - -``` -Workspace linear-client is starting. -Note: this can take about a minute. Run 'brev ls' to check status - -You can safely ctrl+c to exit -``` diff --git a/pkg/cmd/status/status.go b/pkg/cmd/status/status.go deleted file mode 100644 index a05a5986..00000000 --- a/pkg/cmd/status/status.go +++ /dev/null @@ -1,61 +0,0 @@ -package status - -import ( - "github.com/brevdev/brev-cli/pkg/cmd/util" - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/brevdev/brev-cli/pkg/terminal" - "github.com/spf13/cobra" -) - -var ( - createLong = "Create a new Brev machine" - createExample = ` - brev create - ` - // instanceTypes = []string{"p4d.24xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge", "p3dn.24xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "g5.xlarge", "g5.2xlarge", "g5.4xlarge", "g5.8xlarge", "g5.16xlarge", "g5.12xlarge", "g5.24xlarge", "g5.48xlarge", "g5g.xlarge", "g5g.2xlarge", "g5g.4xlarge", "g5g.8xlarge", "g5g.16xlarge", "g5g.metal", "g4dn.xlarge", "g4dn.2xlarge", "g4dn.4xlarge", "g4dn.8xlarge", "g4dn.16xlarge", "g4dn.12xlarge", "g4dn.metal", "g4ad.xlarge", "g4ad.2xlarge", "g4ad.4xlarge", "g4ad.8xlarge", "g4ad.16xlarge", "g3s.xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge"} -) - -type StatusStore interface { - util.GetWorkspaceByNameOrIDErrStore - GetActiveOrganizationOrDefault() (*entity.Organization, error) - GetCurrentUser() (*entity.User, error) - GetWorkspace(workspaceID string) (*entity.Workspace, error) - GetCurrentWorkspaceID() (string, error) - CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) -} - -func NewCmdStatus(t *terminal.Terminal, statusStore StatusStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"workspace": ""}, - Use: "status", - DisableFlagsInUseLine: true, - Short: "About this instance", - Long: createLong, - Example: createExample, - RunE: func(cmd *cobra.Command, args []string) error { - runShowStatus(t, statusStore) - return nil - }, - } - return cmd -} - -func runShowStatus(t *terminal.Terminal, statusStore StatusStore) { - terminal.DisplayBrevLogo(t) - t.Vprintf("\n") - wsID, err := statusStore.GetCurrentWorkspaceID() - if err != nil { - t.Vprintf("\n Error: %s", t.Red(err.Error())) - return - } - ws, err := statusStore.GetWorkspace(wsID) - if err != nil { - t.Vprintf("\n Error: %s", t.Red(err.Error())) - return - } - - t.Vprintf("\nYou're on instance %s", t.Yellow(ws.Name)) - t.Vprintf("\n\tID: %s", t.Yellow(ws.ID)) - t.Vprintf("\n\tMachine: %s", t.Yellow(util.GetInstanceString(*ws))) -} diff --git a/pkg/cmd/status/status_test.go b/pkg/cmd/status/status_test.go deleted file mode 100644 index 6c12ae38..00000000 --- a/pkg/cmd/status/status_test.go +++ /dev/null @@ -1 +0,0 @@ -package status From c9bc38ec12585c8b1239b14248f402b751c4f139 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:57:38 +0000 Subject: [PATCH 5/7] Remove healthcheck command - Remove healthcheck import and registration from cmd.go - Delete pkg/cmd/healthcheck directory and all related files - Healthcheck command was identified as an additional removal request This further streamlines the CLI by removing the backend health checking functionality from the core CLI commands. Co-Authored-By: Alec Fong --- pkg/cmd/cmd.go | 2 -- pkg/cmd/healthcheck/healthcheck.go | 51 ------------------------------ 2 files changed, 53 deletions(-) delete mode 100644 pkg/cmd/healthcheck/healthcheck.go diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 3b87004c..a9395a79 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -9,7 +9,6 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/copy" "github.com/brevdev/brev-cli/pkg/cmd/create" "github.com/brevdev/brev-cli/pkg/cmd/delete" - "github.com/brevdev/brev-cli/pkg/cmd/healthcheck" "github.com/brevdev/brev-cli/pkg/cmd/initfile" "github.com/brevdev/brev-cli/pkg/cmd/invite" "github.com/brevdev/brev-cli/pkg/cmd/login" @@ -240,7 +239,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore)) cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) - cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) cmd.AddCommand(setupworkspace.NewCmdSetupWorkspace(noLoginCmdStore)) } diff --git a/pkg/cmd/healthcheck/healthcheck.go b/pkg/cmd/healthcheck/healthcheck.go deleted file mode 100644 index 2b6cb025..00000000 --- a/pkg/cmd/healthcheck/healthcheck.go +++ /dev/null @@ -1,51 +0,0 @@ -// Package healthcheck checks the health of the backend -package healthcheck - -import ( - "github.com/brevdev/brev-cli/pkg/cmdcontext" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/terminal" - - "github.com/spf13/cobra" -) - -type HealthcheckStore interface { - Healthcheck() error -} - -func NewCmdHealthcheck(t *terminal.Terminal, store HealthcheckStore) *cobra.Command { - cmd := &cobra.Command{ - Annotations: map[string]string{"housekeeping": ""}, - Use: "healthcheck", - Short: "Check backend to see if it's healthy", - Long: "Check backend to see if it's healthy", - Example: `brev healthcheck`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - err := healthcheck(t, store) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - return cmd -} - -func healthcheck(t *terminal.Terminal, store HealthcheckStore) error { - err := store.Healthcheck() - if err != nil { - t.Print("Not Healthy!") - return breverrors.WrapAndTrace(err) - } - t.Print("Healthy!") - return nil -} From 4668cbab17f46ef4cf4568c6ea7024988da9ab90 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 02:00:23 +0000 Subject: [PATCH 6/7] Update terminology: change 'Brev machine' to 'Brev instance' for consistency - Update help text in create, start, stop, portforward, notebook, and scale commands - Preserve 'local machine' and 'host machine' references as they refer to user's computer - Addresses GitHub comment requesting terminology consistency Co-Authored-By: Alec Fong --- pkg/cmd/create/create.go | 2 +- pkg/cmd/notebook/notebook.go | 4 ++-- pkg/cmd/portforward/portforward.go | 4 ++-- pkg/cmd/scale/scale.go | 2 +- pkg/cmd/start/start.go | 2 +- pkg/cmd/stop/stop.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go index e9fbfd32..c3f65a1f 100644 --- a/pkg/cmd/create/create.go +++ b/pkg/cmd/create/create.go @@ -17,7 +17,7 @@ import ( ) var ( - createLong = "Create a new Brev machine" + createLong = "Create a new Brev instance" createExample = ` brev create ` diff --git a/pkg/cmd/notebook/notebook.go b/pkg/cmd/notebook/notebook.go index 186d5ef3..f514eb5d 100644 --- a/pkg/cmd/notebook/notebook.go +++ b/pkg/cmd/notebook/notebook.go @@ -13,7 +13,7 @@ import ( ) var ( - notebookLong = "Open a notebook on your Brev machine" + notebookLong = "Open a notebook on your Brev instance" notebookExample = "brev notebook " ) @@ -29,7 +29,7 @@ type WorkspaceResult struct { func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { cmd := &cobra.Command{ Use: "notebook", - Short: "Open a notebook on your Brev machine", + Short: "Open a notebook on your Brev instance", Long: notebookLong, Example: notebookExample, Args: cobra.ExactArgs(2), diff --git a/pkg/cmd/portforward/portforward.go b/pkg/cmd/portforward/portforward.go index 07b66d2d..294f4f34 100644 --- a/pkg/cmd/portforward/portforward.go +++ b/pkg/cmd/portforward/portforward.go @@ -19,7 +19,7 @@ import ( ) var ( - sshLinkLong = "Port forward your Brev machine's port to your local port" + sshLinkLong = "Port forward your Brev instance's port to your local port" sshLinkExample = "brev port-forward -p local_port:remote_port" ) @@ -145,7 +145,7 @@ func RunSSHPortForward(forwardType string, localPort string, remotePort string, func startInput(t *terminal.Terminal) string { t.Vprint(t.Yellow("\nPorts flag was omitted, running interactive mode!\n")) remoteInput := terminal.PromptGetInput(terminal.PromptContent{ - Label: "What port on your Brev machine would you like to forward?", + Label: "What port on your Brev instance would you like to forward?", ErrorMsg: "error", }) localInput := terminal.PromptGetInput(terminal.PromptContent{ diff --git a/pkg/cmd/scale/scale.go b/pkg/cmd/scale/scale.go index 6e14c4fe..2d18c668 100644 --- a/pkg/cmd/scale/scale.go +++ b/pkg/cmd/scale/scale.go @@ -13,7 +13,7 @@ import ( ) var ( - long = "Scale your Brev instance to get a more powerful machine or save costs" + long = "Scale your Brev instance to get more powerful hardware or save costs" example = ` brev scale MyInstance --gpu p3.2xlarge brev scale MyInstance --cpu 2x8 diff --git a/pkg/cmd/start/start.go b/pkg/cmd/start/start.go index 2ce046ef..62a15875 100644 --- a/pkg/cmd/start/start.go +++ b/pkg/cmd/start/start.go @@ -24,7 +24,7 @@ import ( ) var ( - startLong = "Start a Brev machine that's in a paused or off state or create one from a url" + startLong = "Start a Brev instance that's in a paused or off state or create one from a url" startExample = ` brev start brev start diff --git a/pkg/cmd/stop/stop.go b/pkg/cmd/stop/stop.go index deab8669..3d505a7e 100644 --- a/pkg/cmd/stop/stop.go +++ b/pkg/cmd/stop/stop.go @@ -16,7 +16,7 @@ import ( ) var ( - stopLong = "Stop a Brev machine that's in a running state" + stopLong = "Stop a Brev instance that's in a running state" stopExample = "brev stop ... \nbrev stop --all" ) From a7f8c6400eabc610881ebfea4b7c2270676d97cc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:47:28 +0000 Subject: [PATCH 7/7] Improve UX text: remove 'here' from notebook command - Change 'Click here to go to your Jupyter notebook' to 'Click to go to your Jupyter notebook' - Addresses GitHub comment suggestion from PreciselyAlyss for cleaner UX text Co-Authored-By: Alec Fong --- pkg/cmd/notebook/notebook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/notebook/notebook.go b/pkg/cmd/notebook/notebook.go index f514eb5d..f7fcdd8e 100644 --- a/pkg/cmd/notebook/notebook.go +++ b/pkg/cmd/notebook/notebook.go @@ -62,7 +62,7 @@ func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { fmt.Print("\n" + warningType(" Please keep this terminal open 🤙 ")) - fmt.Print("\nClick here to go to your Jupyter notebook:\n\t 👉" + urlType("http://localhost:8888") + "👈\n\n\n") + fmt.Print("\nClick to go to your Jupyter notebook:\n\t 👉" + urlType("http://localhost:8888") + "👈\n\n\n") // Port forward on 8888 err2 := portforward.RunPortforward(store, args[0], "8888:8888", false)