├── .brev ├── .gitignore ├── nix.sh ├── ports.yaml └── setup.sh ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── library-scripts │ ├── common-debian.sh │ └── docker-in-docker-debian.sh ├── .direnv ├── flake-profile ├── flake-profile-1-link ├── flake-profile-10-link ├── flake-profile-11-link ├── flake-profile-12-link ├── flake-profile-13-link ├── flake-profile-2-link ├── flake-profile-3-link ├── flake-profile-4-link ├── flake-profile-5-link ├── flake-profile-6-link ├── flake-profile-7-link ├── flake-profile-8-link └── flake-profile-9-link ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── Test_CanClonePublicRepoWithoutAuthorizeddKeysAllFormats.yml │ ├── Test_ChangePwd.yml │ ├── Test_CustomBranchCustomSetupCustomFolder.yml │ ├── Test_NoProjectBrev.yml │ ├── Test_NoUserBrevNoProj.yml │ ├── Test_NoUserBrevProj.yml │ ├── Test_ProjectRepoNoBrev.yml │ ├── Test_ProvidedSetupFileChange.yml │ ├── Test_ProvidedSetupRanNoProj.yml │ ├── Test_ProvidedSetupUpdated.yml │ ├── Test_UnauthenticatedSSHKey.yml │ ├── Test_UserBrevProjectBrevV0.yml │ ├── Test_UserBrevProjectBrevV1All.yml │ ├── Test_UserBrevProjectBrevV1Minimal.yml │ ├── Test_VscodeExtension.yml │ ├── Test_httpGit.yml │ ├── build.yml │ ├── codeql-analysis.yml │ ├── fmt.yml │ ├── legacy.yml │ ├── lint.yml │ ├── release.yml │ ├── test.yml │ └── vet.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .projectile ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── assets ├── blank_v0.json ├── std_setup_v0.json ├── test_keypair.json ├── test_noauth_keypair.json ├── test_setup.sh ├── test_setup_v0_norepo.json ├── test_setup_v0_repo.json └── test_workspace.json ├── bin ├── gen-e2e-actions.py ├── install-gateway-linux.sh ├── install-gateway-mac.sh ├── install-gateway-macm1.sh ├── install-latest-linux.sh ├── install-latest.sh ├── newcmd-v2.sh ├── newcmd.sh └── remove-queued-jobs.sh ├── docs └── CONTRIBUTING.md ├── e2etest └── setup │ ├── setup.go │ └── setup_test.go ├── flake-explorations ├── flake-with-both-shell-and-flake.nix ├── flake.lock ├── flake.nix └── result ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── main.go ├── pkg ├── analytics │ └── analytics.go ├── auth │ ├── auth.go │ ├── auth0.go │ ├── auth0_test.go │ ├── auth_test.go │ └── kas.go ├── autostartconf │ ├── aptbinary.go │ ├── autostartconf.go │ ├── darwin.go │ ├── linux.go │ └── staticbinary.go ├── cmd │ ├── background │ │ ├── background.go │ │ └── background_test.go │ ├── clipboard │ │ ├── clipboard.go │ │ └── clipboard_listener.go │ ├── cmd.go │ ├── cmderrors │ │ ├── cmderrors.go │ │ └── cmderrors_test.go │ ├── completions │ │ └── completions.go │ ├── configureenvvars │ │ ├── configureenvvars.go │ │ ├── configureenvvars_test.go │ │ ├── lex.go │ │ └── lex_test.go │ ├── connect │ │ └── connect.go │ ├── create │ │ ├── create.go │ │ ├── create_test.go │ │ └── doc.md │ ├── delete │ │ ├── delete.go │ │ ├── delete_test.go │ │ └── doc.md │ ├── envvars │ │ ├── envvars.go │ │ └── envvars_test.go │ ├── fu │ │ ├── fu.go │ │ └── fu_test.go │ ├── healthcheck │ │ └── healthcheck.go │ ├── hello │ │ ├── hello.go │ │ ├── hello_test.go │ │ ├── onboarding_utils.go │ │ ├── steps.go │ │ └── updateUser.go │ ├── importideconfig │ │ ├── importideconfig.go │ │ └── importideconfig_test.go │ ├── initfile │ │ └── initfile.go │ ├── invite │ │ ├── invite.go │ │ └── invite_test.go │ ├── login │ │ ├── login.go │ │ └── login_test.go │ ├── logout │ │ └── logout.go │ ├── ls │ │ ├── ls.go │ │ └── ls_test.go │ ├── notebook │ │ ├── notebook.go │ │ └── notebook_test.go │ ├── ollama │ │ ├── ollama.go │ │ ├── ollama_test.go │ │ ├── ollamauiverb.yaml │ │ └── ollamaverb.yaml │ ├── open │ │ ├── open.go │ │ └── open_test.go │ ├── org │ │ ├── ls.go │ │ ├── org.go │ │ ├── org_test.go │ │ └── set.go │ ├── paths │ │ ├── paths.go │ │ └── paths_test.go │ ├── portforward │ │ ├── portforward.go │ │ └── portforward_test.go │ ├── profile │ │ ├── profile.go │ │ └── profile_test.go │ ├── proxy │ │ ├── proxy.go │ │ └── proxy_test.go │ ├── recreate │ │ ├── doc.md │ │ └── recreate.go │ ├── refresh │ │ ├── refresh.go │ │ └── refresh_test.go │ ├── reset │ │ ├── doc.md │ │ ├── reset.go │ │ └── reset_test.go │ ├── runtasks │ │ ├── doc.md │ │ ├── runtasks.go │ │ └── runtasks_test.go │ ├── scale │ │ └── scale.go │ ├── secret │ │ ├── secret.go │ │ └── secret_test.go │ ├── set │ │ ├── set.go │ │ └── set_test.go │ ├── setupworkspace │ │ └── setupworkspace.go │ ├── shell │ │ ├── shell.go │ │ └── shell_test.go │ ├── sshkeys │ │ ├── sshkeys.go │ │ └── sshkeys_test.go │ ├── start │ │ ├── doc.md │ │ ├── start.go │ │ └── start_test.go │ ├── status │ │ ├── doc.md │ │ ├── status.go │ │ └── status_test.go │ ├── stop │ │ ├── doc.md │ │ ├── stop.go │ │ └── stop_test.go │ ├── tasks │ │ └── tasks.go │ ├── test │ │ ├── test.go │ │ └── test_test.go │ ├── updatemodel │ │ ├── updatemodel.go │ │ └── updatemodel_test.go │ ├── util │ │ └── util.go │ ├── version │ │ ├── version.go │ │ └── version_test.go │ ├── workspacegroups │ │ └── workspacegroups.go │ └── writeconnectionevent │ │ ├── writeconnectionevent.go │ │ └── writeconnectionevent_test.go ├── cmdcontext │ ├── cmdcontext.go │ └── cmdwriter.go ├── collections │ └── collections.go ├── config │ └── config.go ├── entity │ ├── entity.go │ ├── generic │ │ └── entitygeneric.go │ └── virtualproject │ │ ├── virtualproject.go │ │ └── virtualproject_test.go ├── errors │ ├── errors.go │ └── errors_test.go ├── featureflag │ ├── featureflag.go │ └── featureflag_test.go ├── files │ ├── files.go │ └── files_test.go ├── huproxyclient │ ├── huproxyclient.go │ └── huproxyclient_test.go ├── ids │ └── ids.go ├── instancetypes │ └── instancetypes.go ├── mergeshells │ ├── mergeshells.go │ └── templates │ │ ├── gatsby │ │ └── gatsby │ │ ├── golang │ │ └── golang │ │ ├── node │ │ ├── 14 │ │ └── node │ │ └── rust │ │ └── rust ├── portforward │ ├── portforward.go │ └── portforward_test.go ├── prefixid │ └── prefixid.go ├── remoteversion │ └── remoteversion.go ├── setupscript │ ├── setupscript.go │ └── setupscript_test.go ├── setupworkspace │ ├── setupworkspace.go │ ├── setupworkspace_test.go │ └── validate.go ├── ssh │ ├── ssh.go │ ├── ssh_test.go │ ├── sshconfigurer.go │ ├── sshconfigurer_test.go │ └── tasks.go ├── store │ ├── authtoken.go │ ├── authtoken_test.go │ ├── aws.go │ ├── basic.go │ ├── basic_test.go │ ├── cloudflared.go │ ├── cloudflared_test.go │ ├── file.go │ ├── file_test.go │ ├── healthcheck.go │ ├── http.go │ ├── http_test.go │ ├── jetbrainsgateway.go │ ├── organization.go │ ├── organization_test.go │ ├── release.go │ ├── secret.go │ ├── setupscripturl.go │ ├── ssh.go │ ├── ssh_test.go │ ├── user.go │ ├── user_test.go │ ├── workspace.go │ ├── workspace_test.go │ └── workspacegroup.go ├── tasks │ ├── tasks.go │ └── tasks_test.go ├── terminal │ ├── display.go │ └── terminal.go ├── uri │ └── uri.go ├── util │ ├── util.go │ └── util_test.go ├── workspacemanager │ ├── workspacemanager.go │ └── workspacemanager_test.go ├── workspacemanagerv2 │ ├── containermanager.go │ ├── containermanager_test.go │ ├── volumes.go │ ├── volumes_test.go │ ├── workspacemanagerv2.go │ └── workspacemanagerv2_test.go └── writeconnectionevent │ └── writeconnectionevent.go ├── prod_lite.env ├── shell.nix └── tools ├── go.mod ├── go.sum └── tools.go /.brev/.gitignore: -------------------------------------------------------------------------------- 1 | ports.yaml 2 | logs 3 | -------------------------------------------------------------------------------- /.brev/nix.sh: -------------------------------------------------------------------------------- 1 | # NIX STUFF: 2 | wget https://nixos.org/nix/install -O nix-install 3 | yes | sh nix-install --daemon 4 | sudo sh -c "echo 'build-users-group = nixbld 5 | keep-outputs = true 6 | keep-derivations = true 7 | experimental-features = nix-command flakes 8 | trusted-users = root ubuntu 9 | build-users-group = nixbld 10 | ' > /etc/nix/nix.conf" -------------------------------------------------------------------------------- /.brev/ports.yaml: -------------------------------------------------------------------------------- 1 | ############################################################################################# 2 | ##### Expose your port publicly here so other people or services can use it. ##### 3 | ##### ##### 4 | ##### ex. You want to connect a frontend to a backend. Run the project you want to ##### 5 | ##### expose and add its port here. This service can be accessed at ##### 6 | ##### 3000-projectName-user-org.brev.sh. ##### 7 | ##### ##### 8 | ##### Read more here: https://github.com/brevdev/dotbrev/blob/main/README.md ##### 9 | ############################################################################################# 10 | 11 | # version: "1.0" 12 | # ports: 13 | # - "3000" 14 | # - "5000" 15 | # - "8000" 16 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.6 2 | 3 | # [Option] Install zsh 4 | ARG INSTALL_ZSH="true" 5 | # [Option] Upgrade OS packages to their latest versions 6 | ARG UPGRADE_PACKAGES="false" 7 | # [Option] Enable non-root Docker access in container 8 | ARG ENABLE_NONROOT_DOCKER="true" 9 | # [Option] Use the OSS Moby Engine instead of the licensed Docker Engine 10 | ARG USE_MOBY="true" 11 | 12 | # Install needed packages and setup non-root user. Use a separate RUN statement to add your 13 | # own dependencies. A user of "automatic" attempts to reuse an user ID if one already exists. 14 | ARG USERNAME=automatic 15 | ARG USER_UID=1000 16 | ARG USER_GID=$USER_UID 17 | COPY library-scripts/*.sh /tmp/library-scripts/ 18 | RUN apt-get update \ 19 | && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ 20 | # Use Docker script from script library to set things up 21 | && /bin/bash /tmp/library-scripts/docker-in-docker-debian.sh "${ENABLE_NONROOT_DOCKER}" "${USERNAME}" "${USE_MOBY}" \ 22 | # Clean up 23 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts/ 24 | 25 | VOLUME [ "/var/lib/docker" ] 26 | 27 | # Setting the ENTRYPOINT to docker-init.sh will start up the Docker Engine 28 | # inside the container "overrideCommand": false is set in devcontainer.json. 29 | # The script will also execute CMD if you need to alter startup behaviors. 30 | ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] 31 | CMD [ "sleep", "infinity" ] 32 | 33 | # [Optional] Uncomment this section to install additional OS packages. 34 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 35 | # && apt-get -y install --no-install-recommends 36 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or 2 | // https://github.com/microsoft/vscode-dev-containers 3 | { 4 | "name": "Go with Docker in Docker", 5 | "dockerFile": "Dockerfile", 6 | "runArgs": [ 7 | "--init", 8 | "--privileged" 9 | ], 10 | "overrideCommand": false, 11 | // Set *default* container specific settings.json values on container create. 12 | "settings": { 13 | "terminal.integrated.shell.linux": "/bin/zsh", 14 | "go.gopath": "/go" 15 | }, 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [ 18 | "golang.Go", 19 | "ms-azuretools.vscode-docker" 20 | ], 21 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 22 | // "forwardPorts": [], 23 | // Use 'postCreateCommand' to run commands after the container is created. 24 | "postCreateCommand": "apt-get update && apt-get install -y git && make install" 25 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 26 | // "remoteUser": "vscode" 27 | } -------------------------------------------------------------------------------- /.direnv/flake-profile: -------------------------------------------------------------------------------- 1 | flake-profile-13-link -------------------------------------------------------------------------------- /.direnv/flake-profile-1-link: -------------------------------------------------------------------------------- 1 | /nix/store/8n92g7fw4nix506iwq8nl1fb6ay2sdgc-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-10-link: -------------------------------------------------------------------------------- 1 | /nix/store/bz0fxgpxhnhd5k6pl94yx5b0cnkihjfm-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-11-link: -------------------------------------------------------------------------------- 1 | /nix/store/vlm7h8szz6zk7pw4kbf6rx6wgzz2xkwp-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-12-link: -------------------------------------------------------------------------------- 1 | /nix/store/mnyy3zgc5kaij16qzkfkh7b228y6drg1-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-13-link: -------------------------------------------------------------------------------- 1 | /nix/store/kx2kx5d0fbjdrcmpw8aq3ldv8c1rr3v0-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-2-link: -------------------------------------------------------------------------------- 1 | /nix/store/q19smb3y056p32ziwy4kx7rxmma0m1v0-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-3-link: -------------------------------------------------------------------------------- 1 | /nix/store/3z0dhrbjl6ixk8rnflizdxjsqp363dg4-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-4-link: -------------------------------------------------------------------------------- 1 | /nix/store/9virk8id889lkdr93d5sv03wywybxcrq-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-5-link: -------------------------------------------------------------------------------- 1 | /nix/store/lzbfvrsqj2q22h2b99dcicbhcxcy9bqy-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-6-link: -------------------------------------------------------------------------------- 1 | /nix/store/m12racr5hxc041wzs28iivg142fb91qf-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-7-link: -------------------------------------------------------------------------------- 1 | /nix/store/6znavhsxp7n9x5a6fmjy3mnzjwbpzsw0-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-8-link: -------------------------------------------------------------------------------- 1 | /nix/store/mzhw4r4fwncli7rz89h5lfa6fnx2dnlx-nix-shell-env -------------------------------------------------------------------------------- /.direnv/flake-profile-9-link: -------------------------------------------------------------------------------- 1 | /nix/store/smfaqmssbm1y8qi1iwb6s4a70247j3qg-nix-shell-env -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.{cmd,[cC][mM][dD]} text eol=crlf 3 | *.{bat,[bB][aA][tT]} text eol=crlf 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for Go 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | 14 | # Maintain dependencies for build tools 15 | - package-ecosystem: "gomod" 16 | directory: "/tools" 17 | schedule: 18 | interval: "weekly" 19 | 20 | # Maintain dependencies for GitHub Actions 21 | - package-ecosystem: "github-actions" 22 | directory: "/" 23 | schedule: 24 | interval: "weekly" 25 | -------------------------------------------------------------------------------- /.github/workflows/Test_CanClonePublicRepoWithoutAuthorizeddKeysAllFormats.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_CanClonePublicRepoWithoutAuthorizeddKeysAllFormats 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_CanClonePublicRepoWithoutAuthorizeddKeysAllFormats: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_CanClonePublicRepoWithoutAuthorizeddKeysAllFormats$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_ChangePwd.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_ChangePwd 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_ChangePwd: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_ChangePwd$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_CustomBranchCustomSetupCustomFolder.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_CustomBranchCustomSetupCustomFolder 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_CustomBranchCustomSetupCustomFolder: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_CustomBranchCustomSetupCustomFolder$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_NoProjectBrev.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_NoProjectBrev 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_NoProjectBrev: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_NoProjectBrev$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_NoUserBrevNoProj.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_NoUserBrevNoProj 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_NoUserBrevNoProj: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_NoUserBrevNoProj$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_NoUserBrevProj.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_NoUserBrevProj 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_NoUserBrevProj: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_NoUserBrevProj$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_ProjectRepoNoBrev.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_ProjectRepoNoBrev 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_ProjectRepoNoBrev: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_ProjectRepoNoBrev$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_ProvidedSetupFileChange.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_ProvidedSetupFileChange 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_ProvidedSetupFileChange: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_ProvidedSetupFileChange$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_ProvidedSetupRanNoProj.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_ProvidedSetupRanNoProj 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_ProvidedSetupRanNoProj: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_ProvidedSetupRanNoProj$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_ProvidedSetupUpdated.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_ProvidedSetupUpdated 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_ProvidedSetupUpdated: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_ProvidedSetupUpdated$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_UnauthenticatedSSHKey.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_UnauthenticatedSSHKey 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_UnauthenticatedSSHKey: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_UnauthenticatedSSHKey$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | - name: Report Status 29 | if: always() 30 | uses: ravsamhq/notify-slack-action@v1 31 | with: 32 | status: ${{ job.status }} 33 | notify_when: 'failure' 34 | env: 35 | SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_UserBrevProjectBrevV0.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_UserBrevProjectBrevV0 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_UserBrevProjectBrevV0: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_UserBrevProjectBrevV0$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_UserBrevProjectBrevV1All.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_UserBrevProjectBrevV1All 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_UserBrevProjectBrevV1All: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_UserBrevProjectBrevV1All$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_UserBrevProjectBrevV1Minimal.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_UserBrevProjectBrevV1Minimal 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_UserBrevProjectBrevV1Minimal: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_UserBrevProjectBrevV1Minimal$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_VscodeExtension.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_VscodeExtension 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_VscodeExtension: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_VscodeExtension$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/Test_httpGit.yml: -------------------------------------------------------------------------------- 1 | name: e2etest-Test_httpGit 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | 9 | env: 10 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 11 | 12 | jobs: 13 | Test_httpGit: 14 | 15 | runs-on: [self-hosted] 16 | if: "contains(github.event.head_commit.message, 'e2etest')" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: '1.22.6' 22 | cache: true 23 | - name: expire test cache 24 | run: go clean -testcache 25 | - name: test 26 | run: go test -timeout 240s -run ^Test_httpGit$ github.com/brevdev/brev-cli/e2etest/setup 27 | 28 | # - name: Report Status 29 | # if: always() 30 | # uses: ravsamhq/notify-slack-action@v1 31 | # with: 32 | # status: ${{ job.status }} 33 | # notify_when: 'failure' 34 | # env: 35 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | 28 | - name: Build 29 | run: make fast-build 30 | # - name: Report Status 31 | # if: always() 32 | # uses: ravsamhq/notify-slack-action@v1 33 | # with: 34 | # status: ${{ job.status }} 35 | # notify_when: "failure" 36 | # env: 37 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 38 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | schedule: 10 | - cron: '11 0 * * 5' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'go' ] 21 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 22 | # Learn more: 23 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v2 32 | with: 33 | languages: ${{ matrix.language }} 34 | # If you wish to specify custom queries, you can do so here or in a config file. 35 | # By default, queries listed here will override any specified in a config file. 36 | # Prefix the list here with "+" to use these queries and those in the config file. 37 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 38 | 39 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 40 | # If this step fails, then you should remove it and run the build manually (see below) 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v2 43 | 44 | # ℹ️ Command-line programs to run using the OS shell. 45 | # 📚 https://git.io/JvXDl 46 | 47 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 48 | # and modify them (or add more) to build your code if your project 49 | # uses a compiled language 50 | 51 | #- run: | 52 | # make bootstrap 53 | # make release 54 | 55 | - name: Perform CodeQL Analysis 56 | uses: github/codeql-action/analyze@v2 57 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: fmt 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | fmt: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | 28 | 29 | - name: install 30 | run: make install-tools 31 | - name: check fmt 32 | run: make fmtcheck 33 | # - name: Report Status 34 | # if: always() 35 | # uses: ravsamhq/notify-slack-action@v1 36 | # with: 37 | # status: ${{ job.status }} 38 | # notify_when: 'failure' 39 | # env: 40 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 41 | -------------------------------------------------------------------------------- /.github/workflows/legacy.yml: -------------------------------------------------------------------------------- 1 | name: legacy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | - name: Build 28 | run: make ci 29 | - name: Upload coverage 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: coverage 33 | path: coverage.* 34 | 35 | - name: Upload dist 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: dist 39 | path: dist 40 | 41 | - name: Upload coverage to Codecov 42 | uses: codecov/codecov-action@v3.1.0 43 | with: 44 | file: ./coverage.out 45 | flags: ${{ runner.os }} 46 | 47 | # - name: Report Status 48 | # if: always() 49 | # uses: ravsamhq/notify-slack-action@v1 50 | # with: 51 | # status: ${{ job.status }} 52 | # # notify_when: 'failure' 53 | # env: 54 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 55 | 56 | release-test: 57 | runs-on: ubuntu-22.04 58 | steps: 59 | - uses: actions/checkout@v2 60 | with: 61 | fetch-depth: 0 62 | 63 | - uses: actions/setup-go@v5 64 | with: 65 | go-version: '1.22.6' 66 | cache: true 67 | - name: Release test 68 | run: make build 69 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | - name: install 28 | run: make install-tools 29 | - name: lint 30 | run: make lint 31 | # - name: Report Status 32 | # if: always() 33 | # uses: ravsamhq/notify-slack-action@v1 34 | # with: 35 | # status: ${{ job.status }} 36 | # notify_when: "failure" 37 | # env: 38 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: '1.22.6' 19 | cache: true 20 | - name: Release 21 | run: make ci smoke-test release 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 24 | # disable until this until figure out tags 25 | # - name: Repository Dispatch 26 | # uses: peter-evans/repository-dispatch@v2 27 | # with: 28 | # token: ${{ secrets.WORKSPACE_IMAGES_REPO_ACCESS_TOKEN }} 29 | # event-type: brev-cli-release 30 | # repository: brevdev/workspace-images 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | - name: install 28 | run: make install-tools 29 | - name: test 30 | run: make test 31 | - name: Upload coverage 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: coverage 35 | path: coverage.* 36 | 37 | - name: Upload dist 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: dist 41 | path: dist 42 | - name: Upload coverage to Codecov 43 | uses: codecov/codecov-action@v3.1.0 44 | with: 45 | file: ./coverage.out 46 | flags: ${{ runner.os }} 47 | 48 | - name: Report Status 49 | if: always() 50 | uses: ravsamhq/notify-slack-action@v1 51 | with: 52 | status: ${{ job.status }} 53 | notify_when: "failure" 54 | env: 55 | SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 56 | -------------------------------------------------------------------------------- /.github/workflows/vet.yml: -------------------------------------------------------------------------------- 1 | name: vet 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-22.04] 16 | runs-on: ${{ matrix.os }} 17 | defaults: 18 | run: 19 | shell: bash 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.6' 26 | cache: true 27 | - name: install 28 | run: make install-tools 29 | - name: vet 30 | run: make vet 31 | # - name: Report Status 32 | # if: always() 33 | # uses: ravsamhq/notify-slack-action@v1 34 | # with: 35 | # status: ${{ job.status }} 36 | # notify_when: "failure" 37 | # env: 38 | # SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS DS_Store files 2 | .DS_Store 3 | 4 | __debug_* 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | out 18 | *.out 19 | coverage.html 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | #vendor/ 23 | 24 | # Output of GoReleaser 25 | dist/ 26 | 27 | # Visual Studio Code files 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | *.code-workspace 34 | 35 | # Local History for Visual Studio Code 36 | .history/ 37 | 38 | # GoLand and IntelliJ IDEA files 39 | .idea/ 40 | 41 | # env files that usually contain secrets or local config 42 | # .env 43 | .env 44 | .envrc 45 | 46 | # binary 47 | brev-cli 48 | brev 49 | 50 | # golang executable 51 | go1.* 52 | 53 | devworkspace/** 54 | test.txt 55 | test2.txt 56 | homebrew-brev 57 | flake-explorations -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | goimports: 3 | local-prefixes: github.com/brevdev/brev-cli 4 | revive: 5 | min-confidence: 0.8 6 | rules: 7 | - name: blank-imports 8 | - name: context-as-argument 9 | - name: context-as-argument 10 | - name: context-keys-type 11 | - name: dot-imports 12 | - name: error-return 13 | - name: error-strings 14 | - name: error-naming 15 | - name: if-return 16 | - name: increment-decrement 17 | - name: var-naming 18 | - name: var-declaration 19 | - name: package-comments 20 | - name: range 21 | - name: receiver-naming 22 | - name: time-naming 23 | - name: unexported-return 24 | - name: errorf 25 | - name: empty-block 26 | - name: superfluous-else 27 | - name: unused-parameter 28 | - name: unreachable-code 29 | - name: redefines-builtin-id 30 | gocyclo: 31 | min-complexity: 16 32 | govet: 33 | check-shadowing: true 34 | misspell: 35 | locale: US 36 | nolintlint: 37 | allow-leading-space: false # require machine-readable nolint directives (with no leading space) 38 | allow-unused: false # report any unused nolint directives 39 | require-explanation: true # require an explanation for nolint directives 40 | require-specific: false # don't require nolint directives to be specific about which linter is being skipped 41 | funlen: 42 | lines: 100 43 | wrapcheck: 44 | ignoreSigs: 45 | - .WrapAndTrace 46 | - .Errorf 47 | - .Wrap 48 | - .New 49 | stylecheck: 50 | checks: ["all", "-ST1020", "-ST1000"] 51 | 52 | run: 53 | build-tags: 54 | - codeanalysis 55 | 56 | linters: 57 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 58 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 59 | disable-all: true 60 | enable: 61 | - errcheck 62 | - gosimple 63 | - govet 64 | - ineffassign 65 | # - staticcheck 66 | - typecheck 67 | - unused 68 | - bodyclose 69 | # - depguard 70 | - dupl 71 | - exportloopref 72 | - forcetypeassert 73 | - funlen 74 | # - gci 75 | - gocognit 76 | # - goconst 77 | - gocritic 78 | - gocyclo 79 | # - godot 80 | - gofumpt 81 | # - revive 82 | # - gomnd 83 | - goprintffuncname 84 | - gosec 85 | # - ifshort 86 | - misspell 87 | - noctx 88 | - nolintlint 89 | - rowserrcheck 90 | - sqlclosecheck 91 | - stylecheck 92 | - thelper 93 | - tparallel 94 | - unconvert 95 | - unparam 96 | # - whitespace 97 | # - errorlint 98 | # - goerr113 99 | - wrapcheck 100 | issues: 101 | # enable issues excluded by default 102 | exclude-use-default: false 103 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | builds: 5 | - env: 6 | - CGO_ENABLED=0 7 | goos: 8 | - darwin 9 | - linux 10 | # - windows 11 | goarch: 12 | - amd64 13 | - arm64 14 | binary: brev 15 | ldflags: 16 | - -X github.com/brevdev/brev-cli/pkg/cmd/version.Version={{.Tag}} 17 | 18 | brews: 19 | - name: brev 20 | folder: Formula 21 | # GitHub/GitLab repository to push the formula to 22 | repository: 23 | owner: brevdev 24 | name: homebrew-brev 25 | # Optionally a token can be provided, if it differs from the token provided to GoReleaser 26 | # token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" 27 | # The project name and current git tag are used in the format string. 28 | commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}" 29 | homepage: "https://docs.brev.dev" 30 | description: "CLI tool for managing workspaces provided by brev.dev" 31 | install: | 32 | bin.install "brev" 33 | generate_completions_from_executable("#{bin}/brev", "completion") 34 | test: | 35 | system "#{bin}/brev" "--version" 36 | 37 | archives: 38 | - format_overrides: 39 | - goos: windows 40 | format: zip 41 | release: 42 | github: 43 | prerelease: auto 44 | # dockers: 45 | # - image_templates: 46 | # - "docker.pkg.github.com/brevdev/brev-cli/{{ .ProjectName }}:latest" 47 | # - "docker.pkg.github.com/brevdev/brev-cli/{{ .ProjectName }}:{{ .Major }}" 48 | # - "docker.pkg.github.com/brevdev/brev-cli/{{ .ProjectName }}:{{ .Major }}.{{ .Minor }}" 49 | # - "docker.pkg.github.com/brevdev/brev-cli/{{ .ProjectName }}:{{ .Major }}.{{ .Minor }}.{{ .Patch }}" 50 | # build_flag_templates: 51 | # - "--pull" 52 | # - "--label=org.opencontainers.image.created={{.Date}}" 53 | # - "--label=org.opencontainers.image.name={{.ProjectName}}" 54 | # - "--label=org.opencontainers.image.revision={{.FullCommit}}" 55 | # - "--label=org.opencontainers.image.version={{.Version}}" 56 | # - "--label=org.opencontainers.image.source={{.GitURL}}" 57 | -------------------------------------------------------------------------------- /.projectile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brevdev/brev-cli/2fb523a140c1f2b720968511c32b5ff80e4eaee9/.projectile -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "golang.Go" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.useLanguageServer": true, 3 | "gopls": { 4 | "formatting.gofumpt": true 5 | }, 6 | "[go]": { 7 | "editor.formatOnSave": true, 8 | "editor.codeActionsOnSave": { 9 | "source.organizeImports": "explicit" 10 | } 11 | }, 12 | "[go.mod]": { 13 | "editor.formatOnSave": true, 14 | "editor.codeActionsOnSave": { 15 | "source.organizeImports": "explicit" 16 | } 17 | }, 18 | "go.lintTool": "golangci-lint", 19 | "go.lintFlags": [ 20 | "--fast" 21 | ], 22 | "go.lintOnSave": "workspace", 23 | "go.testTimeout": "240s", 24 | "go.goroot": "/home/ubuntu/.asdf/installs/golang//go", 25 | "[go][go.mod]": { 26 | "editor.codeActionsOnSave": { 27 | "source.organizeImports": "explicit" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "make", 8 | "type": "shell", 9 | "command": "make", 10 | "problemMatcher": [ 11 | "$go" 12 | ], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased](https://github.com/brevdev/brev-cli/compare/v0.0.0...HEAD) 9 | 10 | ### Added 11 | 12 | [WIP] Add tailscale vpn client embedded with Brev. 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY brev / 3 | ENTRYPOINT ["/brev"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Brev.dev Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | # Brev.dev 6 | 7 | [![](https://uohmivykqgnnbiouffke.supabase.co/storage/v1/object/public/landingpage/createdevenv1.svg)](https://console.brev.dev/environment/new?repo=https://github.com/brevdev/brev-cli&instance=2x8) 8 | 9 | [Brev.dev](https://brev.dev) makes it easy to develop on remote machines. Use Brev.dev to start a project and share your intance. 10 | 11 | ## Install the cli 12 | 13 | ### MacOS and Linux 14 | 15 | ``` 16 | brew install brevdev/homebrew-brev/brev 17 | ``` 18 | 19 | ## Get Started 20 | 21 | https://console.brev.dev 22 | 23 | ## Docs 24 | 25 | https://docs.brev.dev 26 | 27 | --- 28 | 29 | https://user-images.githubusercontent.com/14320477/170176621-6b871798-baef-4d42-affe-063a76eca9da.mp4 30 | 31 | ## Contributing 32 | 33 | We welcome PRs! Checkout [Contributing.md](docs/CONTRIBUTING.md) for more. 34 | 35 | ## Trying other badge styles 36 | 37 | [![](https://uohmivykqgnnbiouffke.supabase.co/storage/v1/object/public/landingpage/brevdeployblack.svg?t=2023-08-15T14%3A21%3A03.847Z)](https://console.brev.dev/environment/new?repo=https://github.com/brevdev/brev-cli&instance=2x8) 38 | 39 | [![](https://uohmivykqgnnbiouffke.supabase.co/storage/v1/object/public/landingpage/brevdeploynavy.svg?t=2023-08-15T14%3A21%3A03.847Z)](https://console.brev.dev/environment/new?repo=https://github.com/brevdev/brev-cli&instance=2x8) 40 | -------------------------------------------------------------------------------- /assets/test_noauth_keypair.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicKeyData": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHmlscseepmgVOl5KqhDUHhRVdICXQm8Toh0xq6+NCyL your_email@example.com", 3 | "privateKeyData": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIJKQIBAAKCAgEA8fWyUjqEyTEqtPIXHOvbIz3833Vzp\/PXtszzv8ZJX30M\/29U\r\nGKUAJzEl1yN3VE9\/9sWxoySXzGSm+SVV7jr9KlEKdPHXzxInYgvX8GxjJVzuqImo\r\nfecKmDi2ngU48IK7GyDeITSUgPI6JbHjPC8Sc2+6wVqNiLIzBWAGdIEZ3Txe71GS\r\nwxfMM4jxWTLI4OAH0IpdX5YcDB8oC8SN5ebRe7FhfUI+j0\/H4JFm9x2xLJS9cZuN\r\n7ZR8uRe6n2LqvvokjxPjiJ\/BxjqHNl8hW03gAWcvK2XpL+bsi\/nAu6sZgN92\/Opf\r\nsg2XnL22k3xzx+n8wwxfOOvKEgOIpxHKYfmpCIMNt24\/TtV4f3Vbdqn94AQNd6V9\r\njeKpAFvhjYs8ONNc4VNHpSkBtGCWlxQEsCpDSa9DAUIMdblf3PWKS8KdqOxChpPc\r\nuug0H47Yz5NDLECsG7073awSyvsak3Eo4HPhYza8HYLBlQfHq\/kY3HMFk9m1pvyd\r\nxF6WAB42M65sMlM5RDHlostUp7v\/6+QS4MdrGwqttMhdI6gV1jSVOlQpfsHxWx1V\r\nkYmbOD6LnPziWPUugXaitX0rn9HKOqQA8md0Z75zpBmJsEwcVe0xfMY0XPxRPWqy\r\n2jimDIRIdo5zvB4vTM0GdjHV\/WAaKCvY7ALmXkFPF3OjXFyOariOaIhjZfMCAwEA\r\nAQKCAgBJ1O1LBixKsEQV3uGKo7XEtT+aeF6IW3Hxh+zBEiKFjsUOlMwWkRLQ4sBn\r\nO51IDtI\/XOftnlbrubLxx6DHBx0FcqE8OifeOe3mjzKfXJOMbSUuLINLl9q1xGiF\r\nI5bSXTH2\/zqI62B9UGzJ39Q1IzAAJZHZOmaB1c6Xz0to9ZQM3EUjxcKA4ZwgBaOP\r\n0l1VsUbxLad8aXO5hzBTFvEtvBckJWZYMISienfFYNkIgvjzX5fHOO5AFtVQLZt2\r\n01pKWE4bkrgVkpowgN+Nic3F7Kd0BiZwmbJkOZToyZc0LOulVYsbdfEphdhDregu\r\nbQVvdUj5w6ay2dBZWtayFE2rQ+F97YvTl+Q1AJ8OabydZPEvgIS54P5vDpjg6wWo\r\nnUqn7S2dNVmsuynqShFDkO7\/gMI2dJTC8\/nSHO7i9BWuoPTeeWqnih5gpnoLHgZ+\r\nZDUqbrrE8TZEiIUnfZk+s+8iyWt5KRg9Wp8psj1bXfxxZBbTSVn4K\/iB4MZA1OiC\r\nC\/LcMp0Vi6SKbob6jhJqGoGHrOzs3S+5NP7eLPc9dfGzWbopvbwXhp1p0rdmaHGc\r\nhxYTpDGDWbbKNKqXpMZM1Lf6TPzerIvvzn+pLXN0XAMI7UmANdmuYsQIOA93c3lz\r\nHUcj05AumHtIkKdleUbUKGLYHdAgk9FLqkpGic56LOvYx6\/K0QKCAQEA+I8LvUfI\r\nelpwGbvOKltnsyirmHNwbDX9ujoHguPNHiW0hTn3OJ\/VKxJPsGU+yRLYMpa0vCEU\r\nzS0g4V+CzDR2RDd7kn30bPZRQe8KfedO8hnmMM4AQaOqin\/rl+aBV5WlLxOKZS+X\r\nLjIbcjnVymK8pbjHlhqcvHLl2G6mfh1yXny39KroogG61HRVFrEXNQGmqYX1yg9f\r\nvVLla4TelB6Dv6uHuBGag03JCZaomEMKJxG+wzNE4wVBKJYl6ZuJjNn9kTxAIIUo\r\nD4gvhGLb\/xveVuiy7WoUkM4ROj7ShGviBsojRplDYr1Ji5jao6b\/BN0rf5WlrRy\/\r\nus07xKizJQuwdwKCAQEA+TQTaS4L\/Z26Cj7dtNRP\/ulS2hytIY1PcL8BednTeigD\r\nazdcAARnajZmi4EfteloAP9B1mDeHAlcxgurNEXswf1b5kilFbFa\/FI\/okfv\/4VI\r\nuaC3AHgWZN5la6gh6c3l9m+Gw5ik6g8NweM1CzQD4JWL\/PG79Urk4FnBaX38E9pS\r\nUR8Z4C9Zf0DuBLa7\/yn2hBHTQurspyCDMPhGgeLXrbw4siNQ6d4z1iZ7sZNrPwE+\r\nXwWZkr2TE9a9NJzGXiJX07pEKzSeMHx4WCxY1JUvhFTMYnkY3N7f\/B4W9bLwj7oi\r\nlAx1ZuuQQnBIzjsVxM+jgtib\/9nIsVvzfFVDQgAxZQKCAQEAnyagoqrS4B0GSEPr\r\nZ02toZa6ANxxsKgFdXdwlcuc69\/CrceG13fn+zM3WUAKqp7pVcMPqKIZ+qIZupT4\r\nYB57V4SbGBqUJiy1rN0NP76a2wPgU4GjwmO0cAgmZtXOHbGQ2grOA6osSAUHc+U6\r\nUeNU3VvqV99kWnnLWADJlFjwgTWkaAIDALDQ2vY+AVCVBnivKT7AOYgMimIIygaC\r\nqh67xz9ioGaNI+PrhLs16oCKgKepGL28LwyPQxiY3\/KaaVivNo54lRoNo5xUqJTQ\r\nPpGulMFcyA2za2C2wS+2hdm6GRTW7351GkUPUVYnMMBd69Rd5MyCD80nqsl8qphG\r\nVMMeUwKCAQB6\/NB3oFoamLUwSUZx8DZqwAw7yNtJK8yBAENiN7a\/GvBVAcVN3N6M\r\n9Lw3LUrRJJhHpbKAct4rSBOZSjj8W2Y1dyzbwg53XkhhLtZo6Mfxe34g3shyWtHy\r\nhi\/XqerS0OMldHU2IyeAvF01y0RqewlO1X95HnR84rGCZ8mknqDBy4XEs2y5z6SD\r\nwS+289hkXfljxMhWxkp1UP5uNJnXkHSRMctpXzSXtyouDmANi4vqVFrL2p+oZBcq\r\nO1i1lonv+1MNE2iBSj6n\/0YFfh15DQeeb5tPHiS\/HN++NbtvFxjSVjKqjluCp89S\r\nesfzwAVGVJOGCBE1e+4oWhEY05uV\/zJhAoIBAQDcCH9tLfjSrbdJwsGRRu5eeGy+\r\ndI4oBrD+YmQ48EH01JHunQT9W9XdR6xqomTRf02Ga3oanQQoZgpGXc0yijIaMhIQ\r\nu+Edi9VJCm0rj\/FK6swypksySKhLMQ7DtUvfaP0OlyrJNsCuEEB+kOmkxFkCdjOL\r\ndfRbsZWJh9OcXTQjIvt7Yqs2YHKCG8bqJGlAxp0kOredB3Y2jy7sdgEQKGA3fCQ5\r\nT+aiiljGAP0gBs0Q0r2HToLZowakYd3cjOo\/LCT+0pgdqmxsrBEQKkmVbbERWGIh\r\nOMjWggsIs8S+IyKC8DRFC9JAA45LAmhNedCOnvoQx+wBJajyMZIadr01VYd+\r\n-----END RSA PRIVATE KEY-----\r\n" 4 | } -------------------------------------------------------------------------------- /assets/test_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | echo setup! 6 | -------------------------------------------------------------------------------- /assets/test_workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaceId": "test_workspace_id", 3 | "workspaceGroupId": "test_workspace_group_id" 4 | } -------------------------------------------------------------------------------- /bin/gen-e2e-actions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | 7 | def generate_file_content(test_name): 8 | return ( 9 | f"name: e2etest-{test_name}\n" 10 | + """ 11 | on: 12 | push: 13 | branches: [main] 14 | workflow_dispatch: 15 | 16 | 17 | env: 18 | BREV_SETUP_TEST_CMD_DIR: /home/brev/workspace/actions-runner/_work/brev-cli/brev-cli 19 | 20 | jobs: 21 | """ 22 | + f"{test_name}:\n" 23 | + """ 24 | runs-on: [self-hosted] 25 | if: "contains(github.event.head_commit.message, 'e2etest')" 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: actions/setup-go@v2 29 | with: 30 | go-version: '1.22.6' 31 | cache: true 32 | - name: expire test cache 33 | run: go clean -testcache 34 | - name: test 35 | run: """ 36 | + f"go test -timeout 240s -run ^{test_name}$ github.com/brevdev/brev-cli/e2etest/setup\n" 37 | + """ 38 | """ 39 | ) 40 | 41 | 42 | def create_file_if_not_exist(path): 43 | if not os.path.exists(path): 44 | with open(path, "w") as f: 45 | f.write("") 46 | 47 | 48 | if __name__ == "__main__": 49 | if len(sys.argv) < 2: 50 | print("Usage: python gen-e2e-actions.py ") 51 | sys.exit(1) 52 | 53 | file_path_prefix = [".github", "workflows"] 54 | Path("/".join(file_path_prefix)).mkdir(parents=True, exist_ok=True) 55 | 56 | test_names = (name for name in sys.argv[1:] if name != "") 57 | for test_name in test_names: 58 | path = Path("/".join(file_path_prefix + [test_name + ".yml"])) 59 | create_file_if_not_exist(path) 60 | with open(path, "w") as f: 61 | f.write(generate_file_content(test_name)) 62 | print(f"Generated e2e-{test_name}.yml") 63 | -------------------------------------------------------------------------------- /bin/install-gateway-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Start installation..." 6 | 7 | wget --show-progress -qO ./gateway.tar.gz "https://data.services.jetbrains.com/products/download?code=GW&platform=linux&type=eap,rc,release,beta" 8 | 9 | GATEWAY_TEMP_DIR=$(mktemp -d) 10 | 11 | tar -C "$GATEWAY_TEMP_DIR" -xf gateway.tar.gz 12 | rm ./gateway.tar.gz 13 | 14 | "$GATEWAY_TEMP_DIR"/*/bin/gateway.sh 15 | 16 | rm -r "$GATEWAY_TEMP_DIR" 17 | 18 | echo "JetBrains Gateway was successfully installed!" -------------------------------------------------------------------------------- /bin/install-gateway-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Start installation..." 6 | 7 | wget --show-progress -qO ./gateway.dmg "https://data.services.jetbrains.com/products/download?code=GW&platform=mac&type=eap,rc,release,beta" 8 | 9 | hdiutil attach gateway.dmg 10 | cp -R "/Volumes/JetBrains Gateway/JetBrains Gateway.app" /Applications 11 | hdiutil unmount "/Volumes/JetBrains Gateway" 12 | rm ./gateway.dmg 13 | 14 | echo "JetBrains Gateway successfully installed" 15 | -------------------------------------------------------------------------------- /bin/install-gateway-macm1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Start installation..." 6 | 7 | wget --show-progress -qO ./gateway.dmg "https://data.services.jetbrains.com/products/download?code=GW&platform=mac&type=eap,rc,release,beta" 8 | 9 | hdiutil attach gateway.dmg 10 | cp -R "/Volumes/JetBrains Gateway/JetBrains Gateway.app" /Applications 11 | hdiutil unmount "/Volumes/JetBrains Gateway" 12 | rm ./gateway.dmg 13 | 14 | echo "JetBrains Gateway successfully installed" 15 | -------------------------------------------------------------------------------- /bin/install-latest-linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | # Install the latest version of the Linux binary 5 | DOWNLOAD_URL="$(curl -s https://api.github.com/repos/brevdev/brev-cli/releases/latest | grep "browser_download_url.*linux.*amd64" | cut -d '"' -f 4)" 6 | 7 | # Create temporary directory and ensure cleanup 8 | TMP_DIR="$(mktemp -d)" 9 | trap 'rm -rf "${TMP_DIR}"' EXIT 10 | 11 | # Download the latest release 12 | curl -L "${DOWNLOAD_URL}" -o "${TMP_DIR}/$(basename "${DOWNLOAD_URL}")" 13 | 14 | # Find and extract the archive 15 | ARCHIVE_FILE="$(find "${TMP_DIR}" -name "brev*.tar.gz" -type f)" 16 | tar -xzf "${ARCHIVE_FILE}" -C "${TMP_DIR}" 17 | 18 | # Install the binary to system location 19 | sudo mv "${TMP_DIR}/brev" /usr/local/bin/brev 20 | sudo chmod +x /usr/local/bin/brev 21 | 22 | -------------------------------------------------------------------------------- /bin/install-latest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | # Detect OS and architecture 5 | OS="$(uname -s | tr '[:upper:]' '[:lower:]')" 6 | ARCH="$(uname -m)" 7 | case "${ARCH}" in 8 | x86_64) ARCH="amd64" ;; 9 | aarch64) ARCH="arm64" ;; 10 | esac 11 | 12 | # Get the appropriate download URL for this platform 13 | DOWNLOAD_URL="$(curl -s https://api.github.com/repos/brevdev/brev-cli/releases/latest | grep "browser_download_url.*${OS}.*${ARCH}" | cut -d '"' -f 4)" 14 | 15 | # Verify we found a suitable release 16 | if [ -z "${DOWNLOAD_URL}" ]; then 17 | echo "Error: Could not find release for ${OS} ${ARCH}" >&2 18 | exit 1 19 | fi 20 | 21 | # Create temporary directory and ensure cleanup 22 | TMP_DIR="$(mktemp -d)" 23 | trap 'rm -rf "${TMP_DIR}"' EXIT 24 | 25 | # Download and extract the release 26 | curl -sL "${DOWNLOAD_URL}" -o "${TMP_DIR}/brev.tar.gz" 27 | tar -xzf "${TMP_DIR}/brev.tar.gz" -C "${TMP_DIR}" 28 | 29 | # Install the binary to system location 30 | sudo mv "${TMP_DIR}/brev" /usr/local/bin/brev 31 | sudo chmod +x /usr/local/bin/brev 32 | 33 | echo "Successfully installed brev CLI to /usr/local/bin/brev" 34 | -------------------------------------------------------------------------------- /bin/newcmd-v2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat < pkg/cmd/$1/$1.go 4 | package $1 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | 9 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 10 | "github.com/brevdev/brev-cli/pkg/terminal" 11 | ) 12 | 13 | var ( 14 | short = "TODO" 15 | long = "TODO" 16 | example = "TODO" 17 | ) 18 | 19 | func NewCmd$1(t *terminal.Terminal, store $1Store) *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "$1", 22 | DisableFlagsInUseLine: true, 23 | Short: short, 24 | Long: long, 25 | Example: example, 26 | RunE: $1{ 27 | t: t, 28 | store: store, 29 | }.RunE, 30 | } 31 | return cmd 32 | } 33 | 34 | type $1Store interface {} 35 | 36 | type $1 struct { 37 | t *terminal.Terminal 38 | store $1Store 39 | } 40 | 41 | func ($1 $1) RunE(cmd *cobra.Command, args []string) error { 42 | return breverrors.New("TODO") 43 | } 44 | EOF 45 | -------------------------------------------------------------------------------- /bin/newcmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat < pkg/cmd/$1/$1.go 4 | package $1 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | 9 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 10 | "github.com/brevdev/brev-cli/pkg/terminal" 11 | ) 12 | 13 | var ( 14 | short = "TODO" 15 | long = "TODO" 16 | example = "TODO" 17 | ) 18 | 19 | type $1Store interface {} 20 | 21 | func NewCmd$1(t *terminal.Terminal, store $1Store) *cobra.Command { 22 | cmd := &cobra.Command{ 23 | Use: "$1", 24 | DisableFlagsInUseLine: true, 25 | Short: short, 26 | Long: long, 27 | Example: example, 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | err := Run$1(t, args, store) 30 | if err != nil { 31 | return breverrors.WrapAndTrace(err) 32 | } 33 | return nil 34 | }, 35 | } 36 | return cmd 37 | } 38 | 39 | func Run$1(t *terminal.Terminal, args []string, store $1Store) error { 40 | return nil 41 | } 42 | EOF 43 | -------------------------------------------------------------------------------- /bin/remove-queued-jobs.sh: -------------------------------------------------------------------------------- 1 | token=$GH_TOKEN 2 | repo=brevdev/brev-cli 3 | # get ids of all queued github actions runs for the repo 4 | ids=$(curl -s -H "Authorization: token $token" "https://api.github.com/repos/$repo/actions/runs?status=queued&per_page=100" | jq -r '.workflow_runs[].id') 5 | set -- $ids 6 | for i; do curl \ 7 | -H "Authorization: token $token" \ 8 | -X POST "https://api.github.com/repos/$repo/actions/runs/$i/cancel"; done 9 | -------------------------------------------------------------------------------- /flake-explorations/flake-with-both-shell-and-flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "golang development environment "; 3 | 4 | inputs = { 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 7 | 8 | 9 | }; 10 | 11 | outputs = { self, nixpkgs, flake-utils }: 12 | flake-utils.lib.eachDefaultSystem 13 | (system: 14 | let pkgs = nixpkgs.legacyPackages.${system}; 15 | pkgs.currentSystem = system; 16 | pkgs = import (builtins.fetchGit { 17 | # Descriptive name to make the store path easier to identify 18 | name = "my-old-revision"; 19 | url = "https://github.com/NixOS/nixpkgs/"; 20 | ref = "refs/heads/nixpkgs-unstable"; 21 | rev = "ff8b619cfecb98bb94ae49ca7ceca937923a75fa"; 22 | }) {}; 23 | myPkg = pkgs.golangci-lint; 24 | in 25 | { 26 | # devShell = import ./shell.nix { inherit pkgs; }; 27 | devShell = pkgs.mkShell { buildInputs = [ 28 | pkgs.go_1_18 29 | pkgs.gopls 30 | pkgs.tmux 31 | pkgs.gofumpt 32 | myPkg 33 | pkgs.gosec 34 | pkgs.delve 35 | pkgs.go-tools 36 | pkgs.gotests 37 | pkgs.gomodifytags 38 | ]; currentSystem = system; }; 39 | } 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /flake-explorations/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1659877975, 6 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1665349835, 21 | "narHash": "sha256-UK4urM3iN80UXQ7EaOappDzcisYIuEURFRoGQ/yPkug=", 22 | "owner": "nixos", 23 | "repo": "nixpkgs", 24 | "rev": "34c5293a71ffdb2fe054eb5288adc1882c1eb0b1", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "nixos", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake-explorations/flake.nix: -------------------------------------------------------------------------------- 1 | # { 2 | # inputs = { 3 | # nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | # }; 5 | # outputs = { self, nixpkgs }: 6 | # let 7 | # pkgs = nixpkgs.legacyPackages.x86_64-linux; 8 | # in 9 | # { 10 | # # foo = "bar"; 11 | # packages.x86_64-linux.hello = pkgs.hello; 12 | # packages.x86_64-linux.hello2 = pkgs.cowsay; 13 | # devShell.x86_64-linux = pkgs.mkShell { 14 | # buildInputs = [ self.packages.x86_64-linux.hello self.packages.x86_64-linux.hello2 ]; 15 | # }; 16 | 17 | # }; 18 | # } 19 | 20 | { 21 | inputs = { 22 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 23 | flake-utils.url = "github:numtide/flake-utils"; 24 | }; 25 | outputs = { self, nixpkgs, flake-utils }: 26 | flake-utils.lib.eachDefaultSystem (system: 27 | let 28 | pkgs = nixpkgs.legacyPackages.${system}; 29 | in 30 | { 31 | # foo = "bar"; 32 | packages.hello = pkgs.hello; 33 | 34 | devShell = pkgs.mkShell { buildInputs = [ pkgs.hello pkgs.cowsay ]; }; 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /flake-explorations/result: -------------------------------------------------------------------------------- 1 | /nix/store/gl5a41azbpsadfkfmbilh9yk40dh5dl0-hello-2.12.1 -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1659877975, 6 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1665349835, 21 | "narHash": "sha256-UK4urM3iN80UXQ7EaOappDzcisYIuEURFRoGQ/yPkug=", 22 | "owner": "nixos", 23 | "repo": "nixpkgs", 24 | "rev": "34c5293a71ffdb2fe054eb5288adc1882c1eb0b1", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "nixos", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "golang instance "; 3 | 4 | inputs = { 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem 11 | (system: 12 | let pkgs = nixpkgs.legacyPackages.${system}; in 13 | { 14 | devShell = import ./shell.nix { inherit pkgs system; }; 15 | shellHook = '' 16 | echo "hallo" 17 | ''; 18 | } 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/brevdev/brev-cli/pkg/cmd" 7 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 8 | "github.com/brevdev/brev-cli/pkg/errors" 9 | ) 10 | 11 | func main() { 12 | done := errors.GetDefaultErrorReporter().Setup() 13 | defer done() 14 | command := cmd.NewDefaultBrevCommand() 15 | 16 | if err := command.Execute(); err != nil { 17 | cmderrors.DisplayAndHandleError(err) 18 | done() 19 | os.Exit(1) //nolint:gocritic // manually call done 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/analytics/analytics.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "net/http" 8 | 9 | "github.com/brevdev/brev-cli/pkg/config" 10 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 11 | ) 12 | 13 | type EventData struct { 14 | EventName string `json:"eventName"` 15 | UserID string `json:"userId,omitempty"` 16 | Properties map[string]string `json:"properties,omitempty"` 17 | } 18 | 19 | func TrackEvent(data EventData) error { 20 | conf := config.NewConstants() 21 | 22 | url := conf.GetBrevAPIURl() + "/api/brevent" 23 | 24 | jsonData, err := json.Marshal(data) 25 | if err != nil { 26 | return breverrors.WrapAndTrace(err) 27 | } 28 | 29 | req, err := http.NewRequestWithContext(context.TODO(), "POST", url, bytes.NewBuffer(jsonData)) 30 | if err != nil { 31 | return breverrors.WrapAndTrace(err) 32 | } 33 | 34 | req.Header.Set("Content-Type", "application/json") 35 | 36 | client := &http.Client{} 37 | resp, err := client.Do(req) 38 | if err != nil { 39 | return breverrors.WrapAndTrace(err) 40 | } 41 | 42 | //nolint:errcheck //this is common practice 43 | defer resp.Body.Close() 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/auth/auth0_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | -------------------------------------------------------------------------------- /pkg/autostartconf/aptbinary.go: -------------------------------------------------------------------------------- 1 | package autostartconf 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | 7 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 8 | ) 9 | 10 | type AptBinaryConfigurer struct { 11 | LinuxSystemdConfigurer 12 | URL string 13 | Name string 14 | aptDependencies []string 15 | } 16 | 17 | func (abc AptBinaryConfigurer) Install() error { 18 | _ = abc.UnInstall() // best effort 19 | 20 | // install apt dependencies 21 | err := ExecCommands([][]string{ 22 | {"apt-get", "update"}, 23 | append([]string{"apt-get", "install", "-y"}, abc.aptDependencies...), 24 | }) 25 | if err != nil { 26 | if strings.Contains(err.Error(), "dpkg --configure -a") { 27 | err = ExecCommands([][]string{{"sudo dpkg --configure -a"}}) 28 | if err != nil { 29 | return breverrors.WrapAndTrace(err) 30 | } 31 | } else { 32 | return breverrors.WrapAndTrace(err) 33 | } 34 | } 35 | // download binary 36 | err = abc.Store.DownloadBinary( 37 | abc.URL, 38 | filepath.Join("/usr/local/bin", abc.Name), 39 | ) 40 | if err != nil { 41 | return breverrors.WrapAndTrace(err) 42 | } 43 | 44 | err = abc.Store.WriteString(abc.getDestConfigFile(), abc.ValueConfigFile) 45 | if err != nil { 46 | return breverrors.WrapAndTrace(err) 47 | } 48 | 49 | if ShouldSymlink() { 50 | errother := abc.CreateForcedSymlink() 51 | if errother != nil { 52 | return breverrors.WrapAndTrace(errother) 53 | } 54 | } else { 55 | errother := ExecCommands([][]string{ 56 | {"systemctl", "enable", abc.ServiceName}, 57 | {"systemctl", "start", abc.ServiceName}, 58 | {"systemctl", "daemon-reload"}, 59 | }) 60 | if errother != nil { 61 | return breverrors.WrapAndTrace(errother) 62 | } 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/autostartconf/autostartconf.go: -------------------------------------------------------------------------------- 1 | package autostartconf 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "runtime" 7 | ) 8 | 9 | const ( 10 | targetBin = "/usr/local/bin/brev" 11 | osLinux = "linux" 12 | osDarwin = "darwin" 13 | ) 14 | 15 | type AutoStartStore interface { 16 | CopyBin(targetBin string) error 17 | WriteString(path, data string) error 18 | GetOSUser() string 19 | UserHomeDir() (string, error) 20 | Remove(target string) error 21 | FileExists(target string) (bool, error) 22 | DownloadBinary(url string, target string) error 23 | } 24 | 25 | type DaemonConfigurer interface { 26 | Install() error 27 | UnInstall() error 28 | } 29 | 30 | func ExecCommands(commands [][]string) error { 31 | for _, command := range commands { 32 | first, rest := firstAndRest(command) 33 | out, err := exec.Command(first, rest...).CombinedOutput() // #nosec G204 34 | if err != nil { 35 | return fmt.Errorf("error running %s %s: %v, %s", first, fmt.Sprint(command), err, out) 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | func firstAndRest(commandstring []string) (string, []string) { 42 | first := commandstring[0] 43 | rest := commandstring[1:] 44 | return first, rest 45 | } 46 | 47 | // func NewDaemonConfiguration(user string, command string, serviceName string, serviceType string) DaemonConfigurer { 48 | // switch runtime.GOOS { 49 | // case osLinux: 50 | // return LinuxSystemdConfigurer{ 51 | // } 52 | // case osDarwin: 53 | // } 54 | // } 55 | 56 | func NewSSHConfigurer(store AutoStartStore) DaemonConfigurer { 57 | switch runtime.GOOS { 58 | case osLinux: 59 | return LinuxSystemdConfigurer{ 60 | Store: store, 61 | ValueConfigFile: ` 62 | [Install] 63 | WantedBy=multi-user.target 64 | 65 | [Unit] 66 | Description=Brev ssh configurer daemon 67 | After=systemd-user-sessions.service 68 | 69 | [Service] 70 | Type=simple 71 | ExecStart=brev tasks run sshcd --user ` + store.GetOSUser() + ` 72 | Restart=always 73 | User=` + store.GetOSUser() + ` 74 | `, 75 | ServiceName: "brevsshcd.service", 76 | ServiceType: "user", 77 | TargetBin: targetBin, 78 | } 79 | case osDarwin: 80 | return DarwinPlistConfigurer{ 81 | Store: store, 82 | ValueConfigFile: ` 83 | 84 | 85 | 86 | 87 | 88 | Label 89 | com.brev.sshcd 90 | 91 | ProgramArguments 92 | 93 | /usr/local/bin/brev 94 | tasks 95 | run 96 | sshcd 97 | --user 98 | ` + store.GetOSUser() + ` 99 | 100 | 101 | RunAtLoad 102 | 103 | 104 | StandardOutPath 105 | /var/log/brevsshcd.log 106 | StandardErrorPath 107 | /var/log/brevsshcd.log 108 | 109 | 110 | 111 | `, 112 | ServiceName: "com.brev.sshcd", 113 | ServiceType: SingleUser, 114 | } 115 | } 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/autostartconf/linux.go: -------------------------------------------------------------------------------- 1 | package autostartconf 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 8 | ) 9 | 10 | type LinuxSystemdConfigurer struct { 11 | Store AutoStartStore 12 | ValueConfigFile string 13 | ServiceName string 14 | ServiceType string 15 | TargetBin string 16 | } 17 | 18 | const ( 19 | systemDConfigDir = "/etc/systemd/system/" 20 | ) 21 | 22 | func (lsc LinuxSystemdConfigurer) getDestConfigFile() string { 23 | return path.Join(systemDConfigDir, lsc.ServiceName) 24 | } 25 | 26 | func (lsc LinuxSystemdConfigurer) UnInstall() error { 27 | exists, err := lsc.Store.FileExists(lsc.getDestConfigFile()) 28 | if err != nil { 29 | return breverrors.WrapAndTrace(err) 30 | } 31 | if exists { 32 | errother := lsc.Store.Remove(lsc.getDestConfigFile()) 33 | if errother != nil { 34 | return breverrors.WrapAndTrace(errother) 35 | } 36 | } 37 | err = ExecCommands([][]string{ 38 | {"systemctl", "disable", lsc.ServiceName}, 39 | {"systemctl", "stop", lsc.ServiceName}, 40 | }) 41 | if err != nil { 42 | return breverrors.WrapAndTrace(err) 43 | } 44 | return nil 45 | } 46 | 47 | func (lsc LinuxSystemdConfigurer) Install() error { 48 | _ = lsc.UnInstall() // best effort 49 | err := lsc.Store.CopyBin(lsc.TargetBin) 50 | if err != nil { 51 | return breverrors.WrapAndTrace(err) 52 | } 53 | err = lsc.Store.WriteString(lsc.getDestConfigFile(), lsc.ValueConfigFile) 54 | if err != nil { 55 | return breverrors.WrapAndTrace(err) 56 | } 57 | 58 | if ShouldSymlink() { 59 | errother := lsc.CreateForcedSymlink() 60 | if errother != nil { 61 | return breverrors.WrapAndTrace(errother) 62 | } 63 | } else { 64 | errother := ExecCommands([][]string{ 65 | {"systemctl", "enable", lsc.ServiceName}, 66 | {"systemctl", "start", lsc.ServiceName}, 67 | {"systemctl", "daemon-reload"}, 68 | }) 69 | if errother != nil { 70 | return breverrors.WrapAndTrace(errother) 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | // CreateForcedSymlink aims to be the equivalent operation as running 77 | // ln -sf /lib/systemd/system/huproxy.service /etc/systemd/system/default.target.wants/huproxy.service 78 | // which overwrite's an existing symbolic link to point to a different file 79 | // which we need to do in the workspace docker image because systemd isn't running 80 | // at build time. 81 | func (lsc LinuxSystemdConfigurer) CreateForcedSymlink() error { 82 | symlinkTarget := path.Join("/etc/systemd/system/default.target.wants/", lsc.ServiceName) 83 | err := os.Symlink(lsc.getDestConfigFile(), symlinkTarget) 84 | if err != nil { 85 | return breverrors.WrapAndTrace(err) 86 | } 87 | return nil 88 | } 89 | 90 | func ShouldSymlink() bool { 91 | if os.Getenv("SHOULD_SYMLINK") != "" { 92 | return os.Getenv("SHOULD_SYMLINK") == "1" 93 | } 94 | return false 95 | } 96 | -------------------------------------------------------------------------------- /pkg/autostartconf/staticbinary.go: -------------------------------------------------------------------------------- 1 | package autostartconf 2 | 3 | type StaticBinaryConfigurer struct { 4 | LinuxSystemdConfigurer 5 | URL string 6 | Name string 7 | } 8 | 9 | // best effort 10 | 11 | // download binary 12 | -------------------------------------------------------------------------------- /pkg/cmd/background/background_test.go: -------------------------------------------------------------------------------- 1 | package background 2 | -------------------------------------------------------------------------------- /pkg/cmd/clipboard/clipboard_listener.go: -------------------------------------------------------------------------------- 1 | package clipboard 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | 10 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // Server ... 15 | type Server struct { 16 | host string 17 | port string 18 | } 19 | 20 | // Config ... 21 | type Config struct { 22 | Host string 23 | Port string 24 | } 25 | 26 | // New ... 27 | func CreateListener(config *Config) *Server { 28 | return &Server{ 29 | host: config.Host, 30 | port: config.Port, 31 | } 32 | } 33 | 34 | // tcp req 35 | func SendRequest(address string, message string) error { 36 | reader := strings.NewReader(message) 37 | request, err := http.NewRequestWithContext(context.TODO(), "GET", "http://"+address+"/", reader) 38 | if err != nil { 39 | fmt.Println(err) 40 | return breverrors.WrapAndTrace(err) 41 | } 42 | client := &http.Client{} 43 | resp, err := client.Do(request) 44 | fmt.Println(resp) 45 | if err != nil { 46 | fmt.Println(err) 47 | return breverrors.WrapAndTrace(err) 48 | } 49 | defer resp.Body.Close() //nolint:errcheck //deving and defer 50 | return nil 51 | } 52 | 53 | // Run ... 54 | func (server *Server) Run() { 55 | // Starts a new Gin instance with no middle-ware 56 | r := gin.New() 57 | 58 | r.GET("/", func(c *gin.Context) { 59 | jsonData, err := ioutil.ReadAll(c.Request.Body) 60 | if err != nil { 61 | // Handle error 62 | c.String(http.StatusBadRequest, "Can't parse body") 63 | } 64 | SaveToClipboard(string(jsonData)) 65 | c.String(http.StatusOK, "success") 66 | }) 67 | err := r.Run(server.host + ":" + server.port) 68 | if err != nil { 69 | fmt.Println(err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/cmd/cmderrors/cmderrors.go: -------------------------------------------------------------------------------- 1 | package cmderrors 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/getsentry/sentry-go" 10 | "github.com/pkg/errors" 11 | "github.com/spf13/cobra" 12 | 13 | "github.com/brevdev/brev-cli/pkg/featureflag" 14 | "github.com/brevdev/brev-cli/pkg/terminal" 15 | 16 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 17 | ) 18 | 19 | // determines if should print error stack trace and/or send to crash monitor 20 | 21 | func DisplayAndHandleError(err error) { 22 | er := breverrors.GetDefaultErrorReporter() 23 | er.AddBreadCrumb(breverrors.ErrReportBreadCrumb{ 24 | Type: "default", 25 | Category: "stacktrace", 26 | Level: string(sentry.LevelError), 27 | Message: err.Error(), 28 | }) 29 | if err != nil { 30 | t := terminal.New() 31 | prettyErr := "" 32 | switch errors.Cause(err).(type) { 33 | case breverrors.ValidationError: 34 | // do not report error 35 | prettyErr = (t.Yellow(errors.Cause(err).Error())) 36 | case breverrors.WorkspaceNotRunning: // report error to track when this occurs, but don't print stacktrace to user unless in dev mode 37 | er.ReportError(err) 38 | prettyErr = (t.Yellow(errors.Cause(err).Error())) 39 | case *breverrors.NvidiaMigrationError: 40 | // Handle nvidia migration error 41 | if nvErr, ok := errors.Cause(err).(*breverrors.NvidiaMigrationError); ok { 42 | fmt.Println("\n This account has been migrated to NVIDIA Auth. Attempting to log in with NVIDIA account...") 43 | brevBin, err1 := os.Executable() 44 | if err1 == nil { 45 | cmd := exec.Command(brevBin, "login", "--auth", "nvidia") // #nosec G204 46 | cmd.Stdout = os.Stdout 47 | cmd.Stderr = os.Stderr 48 | cmd.Stdin = os.Stdin 49 | loginErr := cmd.Run() // If automatic login succeeds, we'll exit without showing the original error 50 | if loginErr == nil { 51 | // Login successful, don't show the original error 52 | return 53 | } 54 | } 55 | // Only show the error if automatic login failed or couldn't be attempted 56 | prettyErr = t.Yellow(nvErr.Error() + "\n" + nvErr.Directive()) 57 | } else { 58 | // Fallback in case type assertion fails (shouldn't happen but better safe than sorry) 59 | prettyErr = t.Red(errors.Cause(err).Error()) 60 | er.ReportError(err) 61 | } 62 | default: 63 | if isSneakyValidationErr(err) { 64 | prettyErr = (t.Yellow(errors.Cause(err).Error())) 65 | } else { 66 | er.ReportError(err) 67 | prettyErr = (t.Red(errors.Cause(err).Error())) 68 | } 69 | } 70 | if featureflag.Debug() || featureflag.IsDev() { 71 | fmt.Println(err) 72 | } else { 73 | fmt.Println(prettyErr) 74 | } 75 | } 76 | } 77 | 78 | func isSneakyValidationErr(err error) bool { 79 | return strings.Contains(err.Error(), "unknown flag:") || strings.Contains(err.Error(), "unknown command") 80 | } 81 | 82 | func TransformToValidationError(pa cobra.PositionalArgs) cobra.PositionalArgs { 83 | return func(cmd *cobra.Command, args []string) error { 84 | err := pa(cmd, args) 85 | if err != nil { 86 | return breverrors.NewValidationError(err.Error()) 87 | } 88 | return nil 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pkg/cmd/cmderrors/cmderrors_test.go: -------------------------------------------------------------------------------- 1 | package cmderrors 2 | -------------------------------------------------------------------------------- /pkg/cmd/completions/completions.go: -------------------------------------------------------------------------------- 1 | package completions 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/entity" 5 | "github.com/brevdev/brev-cli/pkg/store" 6 | "github.com/brevdev/brev-cli/pkg/terminal" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type CompletionStore interface { 11 | GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) 12 | GetActiveOrganizationOrDefault() (*entity.Organization, error) 13 | GetCurrentUser() (*entity.User, error) 14 | GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) 15 | } 16 | 17 | type CompletionHandler func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) 18 | 19 | func GetAllWorkspaceNameCompletionHandler(completionStore CompletionStore, t *terminal.Terminal) CompletionHandler { 20 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 21 | user, err := completionStore.GetCurrentUser() 22 | if err != nil { 23 | t.Errprint(err, "") 24 | return nil, cobra.ShellCompDirectiveError 25 | } 26 | 27 | org, err := completionStore.GetActiveOrganizationOrDefault() 28 | if err != nil { 29 | t.Errprint(err, "") 30 | return nil, cobra.ShellCompDirectiveError 31 | } 32 | if org == nil { 33 | return []string{}, cobra.ShellCompDirectiveDefault 34 | } 35 | 36 | workspaces, err := completionStore.GetWorkspaces(org.ID, &store.GetWorkspacesOptions{UserID: user.ID}) 37 | if err != nil { 38 | t.Errprint(err, "") 39 | return nil, cobra.ShellCompDirectiveError 40 | } 41 | 42 | workspaceNames := []string{} 43 | for _, w := range workspaces { 44 | workspaceNames = append(workspaceNames, w.Name) 45 | } 46 | 47 | return workspaceNames, cobra.ShellCompDirectiveDefault 48 | } 49 | } 50 | 51 | func GetOrgsNameCompletionHandler(completionStore CompletionStore, t *terminal.Terminal) CompletionHandler { 52 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 53 | orgs, err := completionStore.GetOrganizations(nil) 54 | if err != nil { 55 | t.Errprint(err, "") 56 | return nil, cobra.ShellCompDirectiveError 57 | } 58 | 59 | orgNames := []string{} 60 | for _, o := range orgs { 61 | orgNames = append(orgNames, o.Name) 62 | } 63 | 64 | return orgNames, cobra.ShellCompDirectiveDefault 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pkg/cmd/connect/connect.go: -------------------------------------------------------------------------------- 1 | package connect 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | ) 9 | 10 | var ( 11 | short = "Connect Brev to your AWS account" 12 | long = "Connect Brev to your AWS account" 13 | example = "brev connect" 14 | ) 15 | 16 | type connectStore interface{} 17 | 18 | func NewCmdConnect(t *terminal.Terminal, store connectStore) *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "connect-aws", 21 | DisableFlagsInUseLine: true, 22 | Short: short, 23 | Long: long, 24 | Example: example, 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | err := RunConnect(t, args, store) 27 | if err != nil { 28 | return breverrors.WrapAndTrace(err) 29 | } 30 | return nil 31 | }, 32 | } 33 | return cmd 34 | } 35 | 36 | func RunConnect(t *terminal.Terminal, _ []string, _ connectStore) error { 37 | t.Vprintf("Connect the AWS IAM user to create instances in your AWS account.\n") 38 | t.Vprintf(t.Yellow("\tFollow the guide here: %s", "https://onboarding.brev.dev/connect-aws\n\n")) 39 | // t.Vprintf(t.Yellow("Connect the AWS IAM user to create dev environments in your AWS account.\n\n")) 40 | 41 | AccessKeyID := terminal.PromptGetInput(terminal.PromptContent{ 42 | Label: "Access Key ID: ", 43 | ErrorMsg: "error", 44 | }) 45 | 46 | SecretAccessKey := terminal.PromptGetInput(terminal.PromptContent{ 47 | Label: "Secret Access Key: ", 48 | ErrorMsg: "error", 49 | Mask: '*', 50 | }) 51 | 52 | t.Vprintf("\n") 53 | t.Vprintf(AccessKeyID) 54 | t.Vprintf(SecretAccessKey) 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/cmd/create/create_test.go: -------------------------------------------------------------------------------- 1 | package create 2 | -------------------------------------------------------------------------------- /pkg/cmd/delete/delete.go: -------------------------------------------------------------------------------- 1 | package delete 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/brevdev/brev-cli/pkg/cmd/completions" 9 | "github.com/brevdev/brev-cli/pkg/cmd/util" 10 | "github.com/brevdev/brev-cli/pkg/entity" 11 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 12 | "github.com/brevdev/brev-cli/pkg/terminal" 13 | "github.com/hashicorp/go-multierror" 14 | "github.com/spf13/cobra" 15 | stripmd "github.com/writeas/go-strip-markdown" 16 | ) 17 | 18 | var ( 19 | //go:embed doc.md 20 | deleteLong string 21 | deleteExample = "brev delete " 22 | ) 23 | 24 | type DeleteStore interface { 25 | completions.CompletionStore 26 | DeleteWorkspace(workspaceID string) (*entity.Workspace, error) 27 | GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) 28 | } 29 | 30 | func NewCmdDelete(t *terminal.Terminal, loginDeleteStore DeleteStore, noLoginDeleteStore DeleteStore) *cobra.Command { 31 | cmd := &cobra.Command{ 32 | Annotations: map[string]string{"workspace": ""}, 33 | Use: "delete", 34 | DisableFlagsInUseLine: true, 35 | Short: "Delete a Brev instance", 36 | Long: stripmd.Strip(deleteLong), 37 | Example: deleteExample, 38 | ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginDeleteStore, t), 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | var allError error 41 | for _, workspace := range args { 42 | err := deleteWorkspace(workspace, t, loginDeleteStore) 43 | if err != nil { 44 | allError = multierror.Append(allError, err) 45 | } 46 | } 47 | if allError != nil { 48 | return breverrors.WrapAndTrace(allError) 49 | } 50 | return nil 51 | }, 52 | } 53 | 54 | return cmd 55 | } 56 | 57 | func deleteWorkspace(workspaceName string, t *terminal.Terminal, deleteStore DeleteStore) error { 58 | workspace, err := util.GetUserWorkspaceByNameOrIDErr(deleteStore, workspaceName) 59 | if err != nil { 60 | err1 := handleAdminUser(err, deleteStore) 61 | if err1 != nil { 62 | return breverrors.WrapAndTrace(err1) 63 | } 64 | } 65 | 66 | var workspaceID string 67 | if workspace != nil { 68 | workspaceID = workspace.ID 69 | } else { 70 | workspaceID = workspaceName 71 | } 72 | 73 | deletedWorkspace, err := deleteStore.DeleteWorkspace(workspaceID) 74 | if err != nil { 75 | return breverrors.WrapAndTrace(err) 76 | } 77 | 78 | t.Vprintf("Deleting instance %s. This can take a few minutes. Run 'brev ls' to check status\n", deletedWorkspace.Name) 79 | 80 | return nil 81 | } 82 | 83 | func handleAdminUser(err error, deleteStore DeleteStore) error { 84 | if strings.Contains(err.Error(), "not found") { 85 | user, err1 := deleteStore.GetCurrentUser() 86 | if err1 != nil { 87 | return breverrors.WrapAndTrace(err1) 88 | } 89 | if user.GlobalUserType != "Admin" { 90 | return breverrors.WrapAndTrace(err) 91 | } 92 | fmt.Println("attempting to delete a workspace you don't own as admin") 93 | return nil 94 | } 95 | 96 | if err != nil { 97 | return breverrors.WrapAndTrace(err) 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/cmd/delete/delete_test.go: -------------------------------------------------------------------------------- 1 | package delete 2 | -------------------------------------------------------------------------------- /pkg/cmd/delete/doc.md: -------------------------------------------------------------------------------- 1 | Delete a Workspace by name or ID. 2 | 3 | ## SYNOPSIS 4 | 5 | ``` 6 | brev delete [ Workspace Name or ID... ] 7 | ``` 8 | 9 | ## DESCRIPTION 10 | 11 | Deleting a workspace will permanently delete a workspace from your account. 12 | This command will delete all content in the workspace and any volumes associated 13 | with the workspace. This command is not reversable and can result in lost work. 14 | 15 | ## EXAMPLE 16 | 17 | ### Delete a workspace 18 | 19 | ``` 20 | $ brev delete payments-frontend 21 | Deleting workspace payments-frontend. This can take a few minutes. Run 'brev ls' to check status 22 | ``` 23 | 24 | #### Delete multiple workspaces 25 | 26 | ``` 27 | $ brev delete bar euler54 naive-pubsub jupyter 28 | Deleting workspace bar. This can take a few minutes. Run 'brev ls' to check status 29 | Deleting workspace euler54. This can take a few minutes. Run 'brev ls' to check status 30 | Deleting workspace naive-pubsub. This can take a few minutes. Run 'brev ls' to check status 31 | Deleting workspace jupyter. This can take a few minutes. Run 'brev ls' to check status 32 | 33 | ``` 34 | 35 | ## SEE ALSO 36 | 37 | TODO 38 | -------------------------------------------------------------------------------- /pkg/cmd/envvars/envvars.go: -------------------------------------------------------------------------------- 1 | package envvars 2 | 3 | import ( 4 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 5 | "github.com/brevdev/brev-cli/pkg/terminal" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type EnvVarsStore interface{} 10 | 11 | func NewCmdEnvVars(_ *terminal.Terminal, evStore EnvVarsStore) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Annotations: map[string]string{"housekeeping": ""}, 14 | Use: "env-vars", 15 | DisableFlagsInUseLine: true, 16 | Short: "configure env vars in supported shells", 17 | Long: "Import your IDE config", 18 | Example: "", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | err := RunEnvVars(evStore) 21 | if err != nil { 22 | return breverrors.WrapAndTrace(err) 23 | } 24 | return nil 25 | }, 26 | } 27 | 28 | return cmd 29 | } 30 | 31 | func RunEnvVars(_ EnvVarsStore) error { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/cmd/envvars/envvars_test.go: -------------------------------------------------------------------------------- 1 | package envvars 2 | -------------------------------------------------------------------------------- /pkg/cmd/fu/fu.go: -------------------------------------------------------------------------------- 1 | package fu 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/brevdev/brev-cli/pkg/cmd/completions" 8 | "github.com/brevdev/brev-cli/pkg/entity" 9 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 10 | "github.com/brevdev/brev-cli/pkg/store" 11 | "github.com/brevdev/brev-cli/pkg/terminal" 12 | "github.com/hashicorp/go-multierror" 13 | "github.com/spf13/cobra" 14 | stripmd "github.com/writeas/go-strip-markdown" 15 | ) 16 | 17 | var ( 18 | fuLong string 19 | fuExample = "brev fu " 20 | ) 21 | 22 | type FuStore interface { 23 | completions.CompletionStore 24 | DeleteWorkspace(workspaceID string) (*entity.Workspace, error) 25 | GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) 26 | BanUser(userID string) error 27 | GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) 28 | GetAllOrgsAsAdmin(userID string) ([]entity.Organization, error) 29 | } 30 | 31 | func NewCmdFu(t *terminal.Terminal, loginFuStore FuStore, noLoginFuStore FuStore) *cobra.Command { 32 | cmd := &cobra.Command{ 33 | Annotations: map[string]string{"workspace": ""}, 34 | Use: "fu", 35 | DisableFlagsInUseLine: true, 36 | Short: "Fetch all workspaces for a user and delete them", 37 | Long: stripmd.Strip(fuLong), 38 | Example: fuExample, 39 | ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginFuStore, t), 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | var allError error 42 | for _, userID := range args { 43 | err := fuUser(userID, t, loginFuStore) 44 | if err != nil { 45 | allError = multierror.Append(allError, err) 46 | } 47 | } 48 | if allError != nil { 49 | return breverrors.WrapAndTrace(allError) 50 | } 51 | return nil 52 | }, 53 | } 54 | 55 | return cmd 56 | } 57 | 58 | func fuUser(userID string, t *terminal.Terminal, fuStore FuStore) error { 59 | orgs, err := fuStore.GetAllOrgsAsAdmin(userID) 60 | if err != nil { 61 | return breverrors.WrapAndTrace(err) 62 | } 63 | 64 | var allWorkspaces []entity.Workspace 65 | for _, org := range orgs { 66 | workspaces, errr := fuStore.GetWorkspaces(org.ID, nil) 67 | if errr != nil { 68 | return breverrors.WrapAndTrace(errr) 69 | } 70 | allWorkspaces = append(allWorkspaces, workspaces...) 71 | } 72 | 73 | s := t.NewSpinner() 74 | s.Suffix = " Fetching workspaces for user " + userID 75 | s.Start() 76 | time.Sleep(5 * time.Second) 77 | s.Stop() 78 | 79 | confirm := terminal.PromptGetInput(terminal.PromptContent{ 80 | Label: fmt.Sprintf("Are you sure you want to delete all %d workspaces for user %s? (y/n)", len(allWorkspaces), userID), 81 | ErrorMsg: "You must confirm to proceed.", 82 | AllowEmpty: false, 83 | }) 84 | if confirm != "y" { 85 | return nil 86 | } 87 | 88 | for _, workspace := range allWorkspaces { 89 | _, err2 := fuStore.DeleteWorkspace(workspace.ID) 90 | if err2 != nil { 91 | t.Vprintf(t.Red("Failed to delete workspace with ID: %s\n", workspace.ID)) 92 | t.Vprintf(t.Red("Error: %s\n", err.Error())) 93 | continue 94 | } 95 | t.Vprintf("✅ Deleted workspace %s\n", workspace.Name) 96 | } 97 | 98 | err = fuStore.BanUser(userID) 99 | if err != nil { 100 | t.Vprintf(t.Red("Failed to ban user with ID: %s\n", userID)) 101 | t.Vprintf(t.Red("Error: %s\n", err.Error())) 102 | } 103 | t.Vprint("\n") 104 | t.Vprintf("🖕 Banned user %s\n", userID) 105 | 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /pkg/cmd/fu/fu_test.go: -------------------------------------------------------------------------------- 1 | package fu 2 | -------------------------------------------------------------------------------- /pkg/cmd/healthcheck/healthcheck.go: -------------------------------------------------------------------------------- 1 | // Package healthcheck checks the health of the backend 2 | package healthcheck 3 | 4 | import ( 5 | "github.com/brevdev/brev-cli/pkg/cmdcontext" 6 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | type HealthcheckStore interface { 13 | Healthcheck() error 14 | } 15 | 16 | func NewCmdHealthcheck(t *terminal.Terminal, store HealthcheckStore) *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Annotations: map[string]string{"housekeeping": ""}, 19 | Use: "healthcheck", 20 | Short: "Check backend to see if it's healthy", 21 | Long: "Check backend to see if it's healthy", 22 | Example: `brev healthcheck`, 23 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 24 | err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) 25 | if err != nil { 26 | return breverrors.WrapAndTrace(err) 27 | } 28 | 29 | return nil 30 | }, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | err := healthcheck(t, store) 33 | if err != nil { 34 | return breverrors.WrapAndTrace(err) 35 | } 36 | return nil 37 | }, 38 | } 39 | 40 | return cmd 41 | } 42 | 43 | func healthcheck(t *terminal.Terminal, store HealthcheckStore) error { 44 | err := store.Healthcheck() 45 | if err != nil { 46 | t.Print("Not Healthy!") 47 | return breverrors.WrapAndTrace(err) 48 | } 49 | t.Print("Healthy!") 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/cmd/hello/hello_test.go: -------------------------------------------------------------------------------- 1 | package hello 2 | -------------------------------------------------------------------------------- /pkg/cmd/importideconfig/importideconfig_test.go: -------------------------------------------------------------------------------- 1 | package importideconfig 2 | -------------------------------------------------------------------------------- /pkg/cmd/initfile/initfile.go: -------------------------------------------------------------------------------- 1 | package initfile 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/mergeshells" 5 | "github.com/brevdev/brev-cli/pkg/terminal" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type InitFileStore interface { 10 | GetFileAsString(path string) (string, error) 11 | } 12 | 13 | func NewCmdInitFile(t *terminal.Terminal, store InitFileStore) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "init", 16 | DisableFlagsInUseLine: true, 17 | Short: "initialize a .brev/setup.sh file if it does not exist", 18 | Long: "initialize a .brev/setup.sh file if it does not exist", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | if len(args) > 0 { 21 | // then assume it is . 22 | mergeshells.ImportPath(t, args[0], store) 23 | } else { 24 | mergeshells.ImportPath(t, ".", store) 25 | } 26 | return nil 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/invite/invite_test.go: -------------------------------------------------------------------------------- 1 | package invite 2 | -------------------------------------------------------------------------------- /pkg/cmd/login/login_test.go: -------------------------------------------------------------------------------- 1 | package login 2 | -------------------------------------------------------------------------------- /pkg/cmd/logout/logout.go: -------------------------------------------------------------------------------- 1 | // Package logout is for the logout command 2 | package logout 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/hashicorp/go-multierror" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 12 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 13 | ) 14 | 15 | type LogoutOptions struct { 16 | auth Auth 17 | store LogoutStore 18 | } 19 | 20 | type Auth interface { 21 | Logout() error 22 | } 23 | 24 | type LogoutStore interface { 25 | ClearDefaultOrganization() error 26 | GetCurrentWorkspaceID() (string, error) 27 | } 28 | 29 | func NewCmdLogout(auth Auth, store LogoutStore) *cobra.Command { 30 | opts := LogoutOptions{ 31 | auth: auth, 32 | store: store, 33 | } 34 | 35 | cmd := &cobra.Command{ 36 | Annotations: map[string]string{"housekeeping": ""}, 37 | Use: "logout", 38 | DisableFlagsInUseLine: true, 39 | Short: "Log out of brev", 40 | Long: "Log out of brev by deleting the credential file", 41 | Example: "brev logout", 42 | Args: cmderrors.TransformToValidationError(cobra.NoArgs), 43 | RunE: func(cmd *cobra.Command, args []string) error { 44 | err := opts.RunLogout() 45 | if err != nil { 46 | return breverrors.WrapAndTrace(err) 47 | } 48 | return nil 49 | }, 50 | } 51 | return cmd 52 | } 53 | 54 | func (o *LogoutOptions) RunLogout() error { 55 | workspaceID, err := o.store.GetCurrentWorkspaceID() 56 | if err != nil { 57 | return breverrors.WrapAndTrace(err) 58 | } 59 | if workspaceID != "" { 60 | return fmt.Errorf("can not logout of workspace") 61 | } 62 | 63 | // best effort 64 | var allErr error 65 | err = o.auth.Logout() 66 | if err != nil { 67 | if !strings.Contains(err.Error(), ".brev/credentials.json: no such file or directory") { 68 | allErr = multierror.Append(err) 69 | } 70 | } 71 | 72 | err = o.store.ClearDefaultOrganization() 73 | if err != nil { 74 | allErr = multierror.Append(err) 75 | } 76 | 77 | if allErr != nil { 78 | return breverrors.WrapAndTrace(allErr) 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /pkg/cmd/ls/ls_test.go: -------------------------------------------------------------------------------- 1 | package ls 2 | -------------------------------------------------------------------------------- /pkg/cmd/notebook/notebook.go: -------------------------------------------------------------------------------- 1 | package notebook 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/cmd/hello" 5 | "github.com/brevdev/brev-cli/pkg/cmd/portforward" 6 | "github.com/brevdev/brev-cli/pkg/cmd/util" 7 | "github.com/brevdev/brev-cli/pkg/entity" 8 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 9 | "github.com/brevdev/brev-cli/pkg/terminal" 10 | "github.com/fatih/color" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | notebookLong = "Open a notebook on your Brev machine" 16 | notebookExample = "brev notebook " 17 | ) 18 | 19 | type NotebookStore interface { 20 | portforward.PortforwardStore 21 | } 22 | 23 | type WorkspaceResult struct { 24 | Workspace *entity.Workspace // Replace with the actual type of workspace returned by GetUserWorkspaceByNameOrIDErr 25 | Err error 26 | } 27 | 28 | func NewCmdNotebook(store NotebookStore, _ *terminal.Terminal) *cobra.Command { 29 | cmd := &cobra.Command{ 30 | Use: "notebook", 31 | Short: "Open a notebook on your Brev machine", 32 | Long: notebookLong, 33 | Example: notebookExample, 34 | Args: cobra.ExactArgs(2), 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | // Channel to get the result of the network call 37 | resultCh := make(chan *WorkspaceResult) 38 | 39 | // Start the network call in a goroutine 40 | go func() { 41 | workspace, err := util.GetUserWorkspaceByNameOrIDErr(store, args[0]) 42 | resultCh <- &WorkspaceResult{Workspace: workspace, Err: err} 43 | }() 44 | 45 | // Type out the checking message 46 | hello.TypeItToMeUnskippable27("Checking to make sure the workspace is running...") 47 | 48 | // Wait for the network call to finish 49 | result := <-resultCh 50 | 51 | if result.Err != nil { 52 | return breverrors.WrapAndTrace(result.Err) 53 | } 54 | 55 | // Check if the workspace is running 56 | if result.Workspace.Status != "RUNNING" { 57 | hello.TypeItToMeUnskippable27("The workspace is not running. Please ensure it's in the running state before proceeding.") 58 | return breverrors.WorkspaceNotRunning{Status: result.Workspace.Status} 59 | } 60 | 61 | urlType := color.New(color.FgCyan, color.Bold).SprintFunc() 62 | warningType := color.New(color.FgBlack, color.Bold, color.BgCyan).SprintFunc() 63 | 64 | hello.TypeItToMeUnskippable("\n" + warningType(" Please keep this terminal open 🤙 ")) 65 | 66 | hello.TypeItToMeUnskippable27("\nClick here to go to your Jupyter notebook:\n\t 👉" + urlType("http://localhost:8888") + "👈\n\n\n") 67 | 68 | // Port forward on 8888 69 | err2 := portforward.RunPortforward(store, args[0], "8888:8888", false) 70 | if err2 != nil { 71 | return breverrors.WrapAndTrace(err2) 72 | } 73 | 74 | // Print out a link for the user 75 | hello.TypeItToMeUnskippable27("Your notebook is accessible at: http://localhost:8888") 76 | 77 | return nil 78 | }, 79 | } 80 | 81 | return cmd 82 | } 83 | -------------------------------------------------------------------------------- /pkg/cmd/notebook/notebook_test.go: -------------------------------------------------------------------------------- 1 | package notebook 2 | -------------------------------------------------------------------------------- /pkg/cmd/ollama/ollama_test.go: -------------------------------------------------------------------------------- 1 | package ollama 2 | -------------------------------------------------------------------------------- /pkg/cmd/ollama/ollamauiverb.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brevdev/brev-cli/2fb523a140c1f2b720968511c32b5ff80e4eaee9/pkg/cmd/ollama/ollamauiverb.yaml -------------------------------------------------------------------------------- /pkg/cmd/ollama/ollamaverb.yaml: -------------------------------------------------------------------------------- 1 | build: 2 | python_version: "3.10" 3 | cuda: 12.0.1 4 | python_packages: 5 | - jupyterlab 6 | run: 7 | - curl -fsSL https://ollama.com/install.sh | sh 8 | user: 9 | shell: zsh 10 | authorized_keys_path: /home/ubuntu/.ssh/authorized_keys 11 | ports: 12 | - "2222:22" 13 | - "8000:8000" 14 | services: 15 | - name: ollama-server 16 | entrypoint: OLLAMA_HOST=0.0.0.0 ollama serve 17 | ports: 18 | - 127.0.0.1:11434:11434 19 | -------------------------------------------------------------------------------- /pkg/cmd/open/open_test.go: -------------------------------------------------------------------------------- 1 | package open 2 | -------------------------------------------------------------------------------- /pkg/cmd/org/ls.go: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 5 | "github.com/brevdev/brev-cli/pkg/cmdcontext" 6 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdOrgLs(t *terminal.Terminal, orgcmdStore OrgCmdStore) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Annotations: map[string]string{"context": ""}, 15 | Use: "ls", 16 | Short: "List your organizations", 17 | Long: `List your organizations, your current org will be prefixed 18 | with * and highlighted with green`, 19 | Example: `brev org ls`, 20 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 21 | err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) 22 | if err != nil { 23 | return breverrors.WrapAndTrace(err) 24 | } 25 | return nil 26 | }, 27 | Args: cmderrors.TransformToValidationError(cobra.NoArgs), 28 | // ValidArgs: []string{"new", "ls"}, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | err := RunOrgs(t, orgcmdStore) 31 | if err != nil { 32 | return breverrors.WrapAndTrace(err) 33 | } 34 | return nil 35 | }, 36 | } 37 | return cmd 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cmd/org/org_test.go: -------------------------------------------------------------------------------- 1 | package org 2 | -------------------------------------------------------------------------------- /pkg/cmd/org/set.go: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 7 | "github.com/brevdev/brev-cli/pkg/cmd/completions" 8 | "github.com/brevdev/brev-cli/pkg/cmdcontext" 9 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 10 | "github.com/brevdev/brev-cli/pkg/store" 11 | "github.com/brevdev/brev-cli/pkg/terminal" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdOrgSet(t *terminal.Terminal, orgcmdStore OrgCmdStore, noorgcmdStore OrgCmdStore) *cobra.Command { 17 | var showAll bool 18 | var org string 19 | 20 | cmd := &cobra.Command{ 21 | Annotations: map[string]string{"context": ""}, 22 | Use: "set", 23 | Short: "Set active org", 24 | Long: "Set your organization to view, open, create workspaces etc", 25 | Example: ` 26 | brev org set 27 | `, 28 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 29 | err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) 30 | if err != nil { 31 | return breverrors.WrapAndTrace(err) 32 | } 33 | 34 | return nil 35 | }, 36 | Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), 37 | // ValidArgs: []string{"new", "ls"}, 38 | RunE: func(cmd *cobra.Command, args []string) error { 39 | err := set(args[0], orgcmdStore, t) 40 | if err != nil { 41 | return breverrors.WrapAndTrace(err) 42 | } 43 | return nil 44 | }, 45 | } 46 | 47 | cmd.Flags().StringVarP(&org, "org", "o", "", "organization (will override active org)") 48 | err := cmd.RegisterFlagCompletionFunc("org", completions.GetOrgsNameCompletionHandler(noorgcmdStore, t)) 49 | if err != nil { 50 | breverrors.GetDefaultErrorReporter().ReportError(breverrors.WrapAndTrace(err)) 51 | fmt.Print(breverrors.WrapAndTrace(err)) 52 | } 53 | 54 | cmd.Flags().BoolVar(&showAll, "all", false, "show all workspaces in org") 55 | 56 | return cmd 57 | } 58 | 59 | func set(orgName string, setStore OrgCmdStore, t *terminal.Terminal) error { 60 | workspaceID, err := setStore.GetCurrentWorkspaceID() 61 | if err != nil { 62 | return breverrors.WrapAndTrace(err) 63 | } 64 | fmt.Println(workspaceID) 65 | 66 | if workspaceID != "" { 67 | return breverrors.NewValidationError("can not set orgs in a workspace") 68 | } 69 | orgs, err := setStore.GetOrganizations(&store.GetOrganizationsOptions{Name: orgName}) 70 | if err != nil { 71 | return breverrors.WrapAndTrace(err) 72 | } 73 | if len(orgs) == 0 { 74 | return breverrors.NewValidationError(fmt.Sprintf("no orgs exist with name %s", orgName)) 75 | } else if len(orgs) > 1 { 76 | return breverrors.NewValidationError(fmt.Sprintf("more than one org exist with name %s", orgName)) 77 | } 78 | 79 | org := orgs[0] 80 | 81 | err = setStore.SetDefaultOrganization(&org) 82 | if err != nil { 83 | return breverrors.WrapAndTrace(err) 84 | } 85 | t.Vprintf("Org %s is now active 🤙\n", t.Green(org.Name)) 86 | 87 | // Print workspaces within org 88 | 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /pkg/cmd/paths/paths.go: -------------------------------------------------------------------------------- 1 | package paths 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | // breverrors "github.com/brevdev/brev-cli/pkg/errors" 7 | ) 8 | 9 | func GetVsCodePaths() []string { 10 | fi, err := ioutil.ReadDir("/home/brev/.vscode-server/bin") 11 | if err != nil { 12 | return []string{} 13 | } 14 | paths := []string{} 15 | for _, f := range fi { 16 | paths = append(paths, fmt.Sprintf("/home/brev/.vscode-server/bin/%s/bin/remote-cli", f.Name())) 17 | } 18 | return paths 19 | } 20 | -------------------------------------------------------------------------------- /pkg/cmd/paths/paths_test.go: -------------------------------------------------------------------------------- 1 | package paths 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetVsCodePaths(_ *testing.T) { 8 | _ = GetVsCodePaths() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/cmd/portforward/portforward_test.go: -------------------------------------------------------------------------------- 1 | package portforward 2 | -------------------------------------------------------------------------------- /pkg/cmd/profile/profile_test.go: -------------------------------------------------------------------------------- 1 | package profile 2 | -------------------------------------------------------------------------------- /pkg/cmd/proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/go-version" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestVersionParsing(t *testing.T) { 11 | _, err := version.NewVersion("abadfjladsf") 12 | assert.NotNil(t, err) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/cmd/recreate/doc.md: -------------------------------------------------------------------------------- 1 | # Re Create Workspace by name or ID. 2 | 3 | ## SYNOPSIS 4 | 5 | ``` 6 | brev recreate [ Workspace Name or ID... ] 7 | ``` 8 | 9 | ## DESCRIPTION 10 | 11 | recreate a workspace is equivalent to running the following commands: 12 | 13 | ``` 14 | brev delete payments-fronted 15 | brev start payments-frontend 16 | ``` 17 | 18 | This command has the effect of updating the base image of a workspace to the 19 | latest. If your workspace has a git remote source, the workspace will start 20 | with a fresh copy of the remote source and run the workspace setupscript. 21 | 22 | ## EXAMPLE 23 | 24 | recreate a workspace with the name `naive-pubsub` 25 | 26 | ``` 27 | $ brev recreate payments-frontend 28 | Starting hard reset 🤙 This can take a couple of minutes. 29 | 30 | Deleting workspace - naive-pubsub. 31 | Workspace is starting. This can take up to 2 minutes the first time. 32 | name naive-pubsub 33 | template v7nd45zsc Admin 34 | resource class 4x16 35 | workspace group brev-test-brevtenant-cluster 36 | You can safely ctrl+c to exit 37 | ⢿ workspace is deploying 38 | Your workspace is ready! 39 | 40 | SSH into your machine: 41 | ssh naive-pubsub-uq0x 42 | ``` 43 | 44 | ## SEE ALSO 45 | 46 | TODO 47 | -------------------------------------------------------------------------------- /pkg/cmd/refresh/refresh_test.go: -------------------------------------------------------------------------------- 1 | package refresh 2 | -------------------------------------------------------------------------------- /pkg/cmd/reset/doc.md: -------------------------------------------------------------------------------- 1 | ## SYNOPSIS 2 | 3 | ``` 4 | brev reset [ Workspace Name or ID... ] 5 | ``` 6 | 7 | ## DESCRIPTION 8 | 9 | reset a workspace will stop a workspace, then start a workspace, perserving 10 | files in `/home/brev/workspace/`. This will have the effect of rerunning your 11 | setupscript in a newley created workspace with no changes made to it, and 12 | replacing your workspace with that. 13 | 14 | ## EXAMPLE 15 | 16 | reset a workspace with the name `payments-frontend` 17 | 18 | ``` 19 | $ brev reset payments-frontend 20 | Workspace payments-frontend is resetting. 21 | Note: this can take a few seconds. Run 'brev ls' to check status 22 | 23 | ``` 24 | 25 | ## SEE ALSO 26 | 27 | TODO 28 | -------------------------------------------------------------------------------- /pkg/cmd/reset/reset_test.go: -------------------------------------------------------------------------------- 1 | package reset 2 | -------------------------------------------------------------------------------- /pkg/cmd/runtasks/doc.md: -------------------------------------------------------------------------------- 1 | ##### Synopsis 2 | 3 | ``` 4 | brev run-tasks -d 5 | ``` 6 | 7 | ##### Description 8 | 9 | In order for brev to connect to workspaces, there needs to be background daemons 10 | running to manage some things on your local machines environment. Currently, the 11 | one that is being launched by run-tasks is an ssh config file configuration 12 | daemon that periodically udpates a ssh config file with connection information 13 | in order to access you workspaces. 14 | 15 | 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 16 | configure this command to be run at boot. 17 | 18 | This command is set to be deprecated in favor of `brev configure`. 19 | 20 | ##### Examples 21 | 22 | to run tasks in the background 23 | 24 | ``` 25 | $ brev run-tasks -d 26 | PID File: /home/f/.brev/task_daemon.pid 27 | Log File: /home/f/.brev/task_daemon.log 28 | ``` 29 | 30 | to run tasks in the foreground 31 | 32 | ``` 33 | $ brev run-tasks 34 | 2022/07/11 15:28:44 creating new ssh config 35 | 2022/07/11 15:28:48 creating new ssh config 36 | 37 | ``` 38 | 39 | ##### See Also 40 | 41 | - [Configuring SSH Proxy Daemon at Boot](https://docs.brev.dev/howto/configure-ssh-proxy-daemon-at-boot/) 42 | -TODO brev configure docs 43 | -------------------------------------------------------------------------------- /pkg/cmd/runtasks/runtasks.go: -------------------------------------------------------------------------------- 1 | package runtasks 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 7 | "github.com/brevdev/brev-cli/pkg/entity" 8 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 9 | "github.com/brevdev/brev-cli/pkg/ssh" 10 | "github.com/brevdev/brev-cli/pkg/tasks" 11 | "github.com/brevdev/brev-cli/pkg/terminal" 12 | "github.com/spf13/cobra" 13 | stripmd "github.com/writeas/go-strip-markdown" 14 | ) 15 | 16 | //go:embed doc.md 17 | var long string 18 | 19 | func NewCmdRunTasks(t *terminal.Terminal, store RunTasksStore) *cobra.Command { 20 | var detached bool 21 | // would be nice to have a way to pass in a list of tasks to run instead of the default 22 | var runRemoteCMD bool 23 | 24 | cmd := &cobra.Command{ 25 | Annotations: map[string]string{"housekeeping": ""}, 26 | Use: "run-tasks", 27 | DisableFlagsInUseLine: true, 28 | Short: "Run background tasks for brev", 29 | Long: stripmd.Strip(long), 30 | Example: "brev run-tasks -d", 31 | Args: cmderrors.TransformToValidationError(cobra.ExactArgs(0)), 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | err := RunTasks(t, store, detached) 34 | if err != nil { 35 | return breverrors.WrapAndTrace(err) 36 | } 37 | return nil 38 | }, 39 | } 40 | 41 | cmd.Flags().BoolVarP(&detached, "detached", "d", false, "run the command in the background instead of blocking the shell") 42 | cmd.Flags().BoolVarP(&runRemoteCMD, "run-remote-cmd", "r", true, "run the command on the instance to cd into ws default dir") 43 | return cmd 44 | } 45 | 46 | type RunTasksStore interface { 47 | ssh.ConfigUpdaterStore 48 | ssh.SSHConfigurerV2Store 49 | tasks.RunTaskAsDaemonStore 50 | GetCurrentUser() (*entity.User, error) 51 | GetCurrentUserKeys() (*entity.UserKeys, error) 52 | } 53 | 54 | func RunTasks(_ *terminal.Terminal, store RunTasksStore, detached bool) error { 55 | ts, err := getDefaultTasks(store) 56 | if err != nil { 57 | return breverrors.WrapAndTrace(err) 58 | } 59 | if detached { 60 | err := tasks.RunTaskAsDaemon(ts, store) 61 | if err != nil { 62 | return breverrors.WrapAndTrace(err) 63 | } 64 | } else { 65 | err := tasks.RunTasks(ts) 66 | if err != nil { 67 | return breverrors.WrapAndTrace(err) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func getDefaultTasks(store RunTasksStore) ([]tasks.Task, error) { 74 | configs, err := ssh.GetSSHConfigs(store) 75 | if err != nil { 76 | return nil, breverrors.WrapAndTrace(err) 77 | } 78 | 79 | // get private key and set here 80 | keys, err := store.GetCurrentUserKeys() 81 | if err != nil { 82 | return nil, breverrors.WrapAndTrace(err) 83 | } 84 | 85 | cu := ssh.NewConfigUpdater(store, configs, keys.PrivateKey) 86 | 87 | return []tasks.Task{cu}, nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/cmd/runtasks/runtasks_test.go: -------------------------------------------------------------------------------- 1 | package runtasks 2 | -------------------------------------------------------------------------------- /pkg/cmd/secret/secret_test.go: -------------------------------------------------------------------------------- 1 | package secret 2 | -------------------------------------------------------------------------------- /pkg/cmd/set/set.go: -------------------------------------------------------------------------------- 1 | // Package set is for the set command 2 | package set 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/brevdev/brev-cli/pkg/cmd/cmderrors" 8 | "github.com/brevdev/brev-cli/pkg/cmd/completions" 9 | "github.com/brevdev/brev-cli/pkg/cmdcontext" 10 | "github.com/brevdev/brev-cli/pkg/entity" 11 | "github.com/brevdev/brev-cli/pkg/store" 12 | "github.com/brevdev/brev-cli/pkg/terminal" 13 | "github.com/spf13/cobra" 14 | 15 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 16 | ) 17 | 18 | type SetStore interface { 19 | completions.CompletionStore 20 | SetDefaultOrganization(org *entity.Organization) error 21 | GetOrganizations(options *store.GetOrganizationsOptions) ([]entity.Organization, error) 22 | GetServerSockFile() string 23 | GetCurrentWorkspaceID() (string, error) 24 | } 25 | 26 | func NewCmdSet(t *terminal.Terminal, loginSetStore SetStore, noLoginSetStore SetStore) *cobra.Command { 27 | cmd := &cobra.Command{ 28 | Annotations: map[string]string{"context": ""}, 29 | Use: "set", 30 | Short: "Set active org (helps with completion)", 31 | Long: "Set your organization to view, open, create instances etc", 32 | Example: `brev set `, 33 | Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), 34 | ValidArgsFunction: completions.GetOrgsNameCompletionHandler(noLoginSetStore, t), 35 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 36 | err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) 37 | if err != nil { 38 | return breverrors.WrapAndTrace(err) 39 | } 40 | return nil 41 | }, 42 | RunE: func(cmd *cobra.Command, args []string) error { 43 | err := set(args[0], loginSetStore) 44 | if err != nil { 45 | return breverrors.WrapAndTrace(err) 46 | } 47 | return nil 48 | }, 49 | } 50 | 51 | return cmd 52 | } 53 | 54 | func set(orgName string, setStore SetStore) error { 55 | workspaceID, err := setStore.GetCurrentWorkspaceID() 56 | if err != nil { 57 | return breverrors.WrapAndTrace(err) 58 | } 59 | if workspaceID != "" { 60 | return fmt.Errorf("can not set orgs in a workspace") 61 | } 62 | orgs, err := setStore.GetOrganizations(&store.GetOrganizationsOptions{Name: orgName}) 63 | if err != nil { 64 | return breverrors.WrapAndTrace(err) 65 | } 66 | if len(orgs) == 0 { 67 | return fmt.Errorf("no orgs exist with name %s", orgName) 68 | } else if len(orgs) > 1 { 69 | return fmt.Errorf("more than one org exist with name %s", orgName) 70 | } 71 | 72 | org := orgs[0] 73 | 74 | err = setStore.SetDefaultOrganization(&org) 75 | if err != nil { 76 | return breverrors.WrapAndTrace(err) 77 | } 78 | 79 | // Print workspaces within org 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/cmd/set/set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | -------------------------------------------------------------------------------- /pkg/cmd/setupworkspace/setupworkspace.go: -------------------------------------------------------------------------------- 1 | package setupworkspace 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/brevdev/brev-cli/pkg/entity" 7 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 8 | "github.com/brevdev/brev-cli/pkg/featureflag" 9 | "github.com/brevdev/brev-cli/pkg/setupworkspace" 10 | "github.com/brevdev/brev-cli/pkg/store" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type SetupWorkspaceStore interface { 15 | GetSetupParams() (*store.SetupParamsV0, error) 16 | WriteSetupScript(script string) error 17 | GetSetupScriptPath() string 18 | GetCurrentUser() (*entity.User, error) 19 | GetCurrentWorkspaceID() (string, error) 20 | } 21 | 22 | const Name = "setupworkspace" 23 | 24 | // Internal command for setting up workspace // v1 similar to k8s post-start script 25 | func NewCmdSetupWorkspace(store SetupWorkspaceStore) *cobra.Command { 26 | var forceEnableSetup bool 27 | cmd := &cobra.Command{ 28 | Annotations: map[string]string{"hidden": ""}, 29 | Use: Name, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | breverrors.GetDefaultErrorReporter().AddTag("command", Name) 32 | _, err := store.GetCurrentWorkspaceID() // do this to error reporting 33 | if err != nil { 34 | return breverrors.WrapAndTrace(err) 35 | } 36 | fmt.Println("setting up instance") 37 | 38 | params, err := store.GetSetupParams() 39 | if err != nil { 40 | return breverrors.WrapAndTrace(err) 41 | } 42 | 43 | if !featureflag.IsDev() { 44 | _, err = store.GetCurrentUser() // do this to set error user reporting 45 | if err != nil { 46 | fmt.Println(err) 47 | if !params.DisableSetup { 48 | breverrors.GetDefaultErrorReporter().ReportError(breverrors.Wrap(err, "setup continued")) 49 | } 50 | } 51 | } 52 | 53 | if !forceEnableSetup && params.DisableSetup { 54 | fmt.Printf("WARNING: setup script not running [params.DisableSetup=%v, forceEnableSetup=%v]", params.DisableSetup, forceEnableSetup) 55 | return nil 56 | } 57 | 58 | err = setupworkspace.SetupWorkspace(params) 59 | if err != nil { 60 | return breverrors.WrapAndTrace(err) 61 | } 62 | fmt.Println("done setting up instance") 63 | return nil 64 | }, 65 | } 66 | cmd.PersistentFlags().BoolVar(&forceEnableSetup, "force-enable", false, "force the setup script to run despite params") 67 | 68 | return cmd 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/shell/shell_test.go: -------------------------------------------------------------------------------- 1 | package shell 2 | -------------------------------------------------------------------------------- /pkg/cmd/sshkeys/sshkeys.go: -------------------------------------------------------------------------------- 1 | // Package sshkeys gets your public ssh key to add to github/gitlab 2 | package sshkeys 3 | 4 | import ( 5 | "github.com/brevdev/brev-cli/pkg/cmdcontext" 6 | "github.com/brevdev/brev-cli/pkg/entity" 7 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 8 | "github.com/brevdev/brev-cli/pkg/terminal" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type SSHKeyStore interface { 14 | GetCurrentUser() (*entity.User, error) 15 | } 16 | 17 | func NewCmdSSHKeys(t *terminal.Terminal, sshKeyStore SSHKeyStore) *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Annotations: map[string]string{"housekeeping": ""}, 20 | Use: "ssh-key", 21 | Short: "Get your pulic SSH-Key", 22 | Long: "Get your pulic SSH-Key to add to pull and push from your git repository.", 23 | Example: `brev ssh-key`, 24 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 25 | err := cmdcontext.InvokeParentPersistentPreRun(cmd, args) 26 | if err != nil { 27 | return breverrors.WrapAndTrace(err) 28 | } 29 | 30 | return nil 31 | }, 32 | Args: cobra.NoArgs, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | user, err := sshKeyStore.GetCurrentUser() 35 | if err != nil { 36 | return breverrors.WrapAndTrace(err) 37 | } 38 | DisplaySSHKeys(t, user.PublicKey) 39 | return nil 40 | }, 41 | } 42 | 43 | return cmd 44 | } 45 | 46 | func DisplaySSHKeys(t *terminal.Terminal, publicKey string) { 47 | t.Vprintf(publicKey) 48 | t.Print("\n") 49 | t.Eprintf(t.Yellow("Copy 👆 and add it to your git provider:\n")) 50 | t.Eprintf(t.Yellow("\tGithub: https://github.com/settings/keys\n")) 51 | t.Eprintf(t.Yellow("\tGitlab: https://gitlab.com/-/profile/keys\n")) 52 | t.Eprintf(t.Yellow("Check authentication by starting a new instance\n")) 53 | t.Eprintf(t.Yellow("\tbrev start --empty --name test-ssh && brev delete test-ssh\n")) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/cmd/sshkeys/sshkeys_test.go: -------------------------------------------------------------------------------- 1 | package sshkeys 2 | -------------------------------------------------------------------------------- /pkg/cmd/start/start_test.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brevdev/brev-cli/pkg/entity" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMakeNewWorkspaceFromURL(t *testing.T) { 12 | gitTruth := "github.com:brevdev/brev-cli.git" 13 | nameTruth := "brev-cli" 14 | 15 | wksTruth := NewWorkspace{ 16 | Name: nameTruth, 17 | GitRepo: gitTruth, 18 | } 19 | 20 | naked := "https://github.com/brevdev/brev-cli" 21 | res := MakeNewWorkspaceFromURL(naked) 22 | if !assert.Equal(t, wksTruth, res) { 23 | return 24 | } 25 | 26 | http := "http://github.com/brevdev/brev-cli.git" 27 | res = MakeNewWorkspaceFromURL(http) 28 | if !assert.Equal(t, wksTruth, res) { 29 | return 30 | } 31 | 32 | https := "https://github.com/brevdev/brev-cli.git" 33 | res = MakeNewWorkspaceFromURL(https) 34 | if !assert.Equal(t, wksTruth, res) { 35 | return 36 | } 37 | 38 | ssh := "git@github.com:brevdev/brev-cli.git" 39 | res = MakeNewWorkspaceFromURL(ssh) 40 | if !assert.Equal(t, wksTruth, res) { 41 | return 42 | } 43 | } 44 | 45 | func Test_DisplayBC(t *testing.T) { 46 | term := terminal.New() 47 | displayConnectBreadCrumb(term, &entity.Workspace{ 48 | ID: "123456789", 49 | Name: "my-name", 50 | WorkspaceGroupID: "", 51 | OrganizationID: "", 52 | WorkspaceClassID: "", 53 | CreatedByUserID: "", 54 | DNS: "", 55 | Status: "", 56 | Password: "", 57 | GitRepo: "", 58 | Version: "", 59 | WorkspaceTemplate: entity.WorkspaceTemplate{}, 60 | NetworkID: "", 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/cmd/status/status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/cmd/util" 5 | "github.com/brevdev/brev-cli/pkg/entity" 6 | "github.com/brevdev/brev-cli/pkg/store" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | createLong = "Create a new Brev machine" 13 | createExample = ` 14 | brev create 15 | ` 16 | // 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"} 17 | ) 18 | 19 | type StatusStore interface { 20 | util.GetWorkspaceByNameOrIDErrStore 21 | GetActiveOrganizationOrDefault() (*entity.Organization, error) 22 | GetCurrentUser() (*entity.User, error) 23 | GetWorkspace(workspaceID string) (*entity.Workspace, error) 24 | GetCurrentWorkspaceID() (string, error) 25 | CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error) 26 | } 27 | 28 | func NewCmdStatus(t *terminal.Terminal, statusStore StatusStore) *cobra.Command { 29 | cmd := &cobra.Command{ 30 | Annotations: map[string]string{"workspace": ""}, 31 | Use: "status", 32 | DisableFlagsInUseLine: true, 33 | Short: "About this instance", 34 | Long: createLong, 35 | Example: createExample, 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | runShowStatus(t, statusStore) 38 | return nil 39 | }, 40 | } 41 | return cmd 42 | } 43 | 44 | func runShowStatus(t *terminal.Terminal, statusStore StatusStore) { 45 | terminal.DisplayBrevLogo(t) 46 | t.Vprintf("\n") 47 | wsID, err := statusStore.GetCurrentWorkspaceID() 48 | if err != nil { 49 | t.Vprintf("\n Error: %s", t.Red(err.Error())) 50 | return 51 | } 52 | ws, err := statusStore.GetWorkspace(wsID) 53 | if err != nil { 54 | t.Vprintf("\n Error: %s", t.Red(err.Error())) 55 | return 56 | } 57 | 58 | t.Vprintf("\nYou're on instance %s", t.Yellow(ws.Name)) 59 | t.Vprintf("\n\tID: %s", t.Yellow(ws.ID)) 60 | t.Vprintf("\n\tMachine: %s", t.Yellow(util.GetInstanceString(*ws))) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/cmd/status/status_test.go: -------------------------------------------------------------------------------- 1 | package status 2 | -------------------------------------------------------------------------------- /pkg/cmd/stop/doc.md: -------------------------------------------------------------------------------- 1 | ## EXAMPLES 2 | 3 | stop multiple workspaces 4 | 5 | ``` 6 | $ brev stop brev-deploy naive-pubsub bar euler54 merge-json 7 | Workspace brev-deploy is stopping. 8 | Note: this can take a few seconds. Run 'brev ls' to check status 9 | Workspace naive-pubsub is stopping. 10 | Note: this can take a few seconds. Run 'brev ls' to check status 11 | Workspace bar is stopping. 12 | Note: this can take a few seconds. Run 'brev ls' to check status 13 | Workspace euler54 is stopping. 14 | Note: this can take a few seconds. Run 'brev ls' to check status 15 | Workspace merge-json is stopping. 16 | Note: this can take a few seconds. Run 'brev ls' to check status 17 | ``` 18 | -------------------------------------------------------------------------------- /pkg/cmd/stop/stop_test.go: -------------------------------------------------------------------------------- 1 | package stop 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStopWorkspaceSelf(_ *testing.T) { 8 | // err := stopThisWorkspace(nil, nil) 9 | // assert.Nil(t, err) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/cmd/test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/brevdev/brev-cli/pkg/autostartconf" 7 | "github.com/brevdev/brev-cli/pkg/cmd/completions" 8 | "github.com/brevdev/brev-cli/pkg/entity" 9 | "github.com/brevdev/brev-cli/pkg/store" 10 | "github.com/brevdev/brev-cli/pkg/terminal" 11 | "github.com/brevdev/brev-cli/pkg/util" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var ( 17 | startLong = "[internal] test" 18 | startExample = "[internal] test" 19 | ) 20 | 21 | type TestStore interface { 22 | completions.CompletionStore 23 | ResetWorkspace(workspaceID string) (*entity.Workspace, error) 24 | GetAllWorkspaces(options *store.GetWorkspacesOptions) ([]entity.Workspace, error) 25 | GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) 26 | GetActiveOrganizationOrDefault() (*entity.Organization, error) 27 | GetCurrentUser() (*entity.User, error) 28 | GetWorkspace(id string) (*entity.Workspace, error) 29 | GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) 30 | CopyBin(targetBin string) error 31 | GetSetupScriptContentsByURL(url string) (string, error) 32 | UpdateUser(userID string, updatedUser *entity.UpdateUser) (*entity.User, error) 33 | } 34 | 35 | type ServiceMeshStore interface { 36 | autostartconf.AutoStartStore 37 | GetWorkspace(workspaceID string) (*entity.Workspace, error) 38 | } 39 | 40 | func NewCmdTest(_ *terminal.Terminal, _ TestStore) *cobra.Command { 41 | cmd := &cobra.Command{ 42 | Annotations: map[string]string{"devonly": ""}, 43 | Use: "test", 44 | DisableFlagsInUseLine: true, 45 | Short: "[internal] Test random stuff.", 46 | Long: startLong, 47 | Example: startExample, 48 | // Args: cmderrors.TransformToValidationError(cobra.MinimumNArgs(1)), 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | // fmt.Printf("NAME ID URL SOMETHING ELSE") 51 | // hello.TypeItToMe("\n\n\n") 52 | // hello.TypeItToMe("👆 this is the name of your environment (which you can use to open the environment)") 53 | // time.Sleep(1 * time.Second) 54 | // fmt.Printf("\332K\r") 55 | // fmt.Println(" ") 56 | // hello.TypeItToMe(" 👆 you can expose your localhost to this public URL") 57 | // time.Sleep(1 * time.Second) 58 | // fmt.Printf("\332K\r") 59 | // fmt.Printf("bye world") 60 | // fmt.Printf("bye world") 61 | 62 | // s := t.Yellow("\n\nCould you please install the following VSCode extension? %s", t.Green("ms-vscode-remote.remote-ssh")) 63 | // s += "\nDo that then run " + t.Yellow("brev hello") + " to resume this walk-through\n" 64 | // // s += "Here's a video of me installing the VS Code extension 👉 " + "" 65 | // hello.TypeItToMe(s) 66 | 67 | res := util.DoesPathExist("/Users/naderkhalil/brev-cli") 68 | // res := util.DoesPathExist("/home/brev/workspace") 69 | fmt.Println(res) 70 | 71 | return nil 72 | }, 73 | } 74 | 75 | return cmd 76 | } 77 | -------------------------------------------------------------------------------- /pkg/cmd/test/test_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | -------------------------------------------------------------------------------- /pkg/cmd/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/brevdev/brev-cli/pkg/entity" 7 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 8 | "github.com/brevdev/brev-cli/pkg/store" 9 | ) 10 | 11 | type GetWorkspaceByNameOrIDErrStore interface { 12 | GetActiveOrganizationOrDefault() (*entity.Organization, error) 13 | GetWorkspaceByNameOrID(orgID string, nameOrID string) ([]entity.Workspace, error) 14 | GetCurrentUser() (*entity.User, error) 15 | } 16 | 17 | func GetUserWorkspaceByNameOrIDErr(storeQ GetWorkspaceByNameOrIDErrStore, workspaceNameOrID string) (*entity.Workspace, error) { 18 | user, err := storeQ.GetCurrentUser() 19 | if err != nil { 20 | return nil, breverrors.WrapAndTrace(err) 21 | } 22 | org, err := storeQ.GetActiveOrganizationOrDefault() 23 | if err != nil { 24 | return nil, breverrors.WrapAndTrace(err) 25 | } 26 | workspaces, err := storeQ.GetWorkspaceByNameOrID(org.ID, workspaceNameOrID) 27 | if err != nil { 28 | return nil, breverrors.WrapAndTrace(err) 29 | } 30 | 31 | workspaces = store.FilterForUserWorkspaces(workspaces, user.ID) 32 | if len(workspaces) == 0 { 33 | return nil, breverrors.NewValidationError(fmt.Sprintf("instance with id/name %s not found", workspaceNameOrID)) 34 | } 35 | return &workspaces[0], nil 36 | } 37 | 38 | func GetAnyWorkspaceByIDOrNameInActiveOrgErr(storeQ GetWorkspaceByNameOrIDErrStore, workspaceNameOrID string) (*entity.Workspace, error) { 39 | org, err := storeQ.GetActiveOrganizationOrDefault() 40 | if err != nil { 41 | return nil, breverrors.WrapAndTrace(err) 42 | } 43 | workspaces, err := storeQ.GetWorkspaceByNameOrID(org.ID, workspaceNameOrID) 44 | if err != nil { 45 | return nil, breverrors.WrapAndTrace(err) 46 | } 47 | 48 | if len(workspaces) == 0 { 49 | return nil, breverrors.NewValidationError(fmt.Sprintf("instance with id/name %s not found", workspaceNameOrID)) 50 | } 51 | if len(workspaces) > 1 { 52 | workspaces = store.FilterNonFailedWorkspaces(workspaces) 53 | if len(workspaces) == 0 { 54 | return nil, breverrors.NewValidationError(fmt.Sprintf("instance with id/name %s is a failed workspace", workspaceNameOrID)) 55 | } 56 | if len(workspaces) > 1 { 57 | return nil, breverrors.NewValidationError(fmt.Sprintf("multiple instances found with id/name %s", workspaceNameOrID)) 58 | } 59 | } 60 | return &workspaces[0], nil 61 | } 62 | 63 | type MakeWorkspaceWithMetaStore interface { 64 | GetWorkspaceMetaData(workspaceID string) (*entity.WorkspaceMetaData, error) 65 | } 66 | 67 | func GetClassIDString(classID string) string { 68 | // switch statement on class ID 69 | switch classID { 70 | case "2x2": 71 | return "2 cpu | 2 gb ram" 72 | case "2x4": 73 | return "2 cpu | 4 gb ram" 74 | case "2x8": 75 | return "2 cpu | 8 gb ram" 76 | case "4x16": 77 | return "4 cpu | 16 gb ram" 78 | case "8x32": 79 | return "8 cpu | 32 gb ram" 80 | case "16x32": 81 | return "16 cpu | 32 gb ram" 82 | default: 83 | return classID 84 | 85 | } 86 | } 87 | 88 | func GetInstanceString(w entity.Workspace) string { 89 | var instanceString string 90 | if w.WorkspaceClassID != "" { 91 | instanceString = GetClassIDString(w.WorkspaceClassID) 92 | } else { 93 | instanceString = w.InstanceType + " (gpu)" 94 | } 95 | return instanceString 96 | } 97 | -------------------------------------------------------------------------------- /pkg/cmd/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var Version = "" 4 | -------------------------------------------------------------------------------- /pkg/cmd/version/version_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBuildVersionString(_ *testing.T) { 8 | // func TestBuildVersionString(t *testing.T) { 9 | // terminalStub := &terminal.Terminal{} 10 | 11 | // want := "unknown" 12 | // got, err := buildVersionString(terminalStub) 13 | 14 | // if want != got || err != nil { 15 | // t.Errorf(`buildVersionString() = %q, %v, want match for %#q, nil`, got, err, want) 16 | // } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/cmd/workspacegroups/workspacegroups.go: -------------------------------------------------------------------------------- 1 | package workspacegroups 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jedib0t/go-pretty/v6/table" 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/brevdev/brev-cli/pkg/entity" 10 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 11 | "github.com/brevdev/brev-cli/pkg/terminal" 12 | ) 13 | 14 | type WorkspaceGroupsStore interface { 15 | GetWorkspaceGroups(organizationID string) ([]entity.WorkspaceGroup, error) 16 | GetActiveOrganizationOrDefault() (*entity.Organization, error) 17 | } 18 | 19 | func NewCmdWorkspaceGroups(t *terminal.Terminal, store WorkspaceGroupsStore) *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "workspacegroups", 22 | DisableFlagsInUseLine: true, 23 | Short: "TODO", 24 | Long: "TODO", 25 | Example: "TODO", 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | err := RunWorkspaceGroups(t, args, store) 28 | if err != nil { 29 | return breverrors.WrapAndTrace(err) 30 | } 31 | return nil 32 | }, 33 | } 34 | return cmd 35 | } 36 | 37 | func RunWorkspaceGroups(_ *terminal.Terminal, _ []string, store WorkspaceGroupsStore) error { 38 | org, err := store.GetActiveOrganizationOrDefault() 39 | if err != nil { 40 | return breverrors.WrapAndTrace(err) 41 | } 42 | wsgs, err := store.GetWorkspaceGroups(org.ID) 43 | if err != nil { 44 | return breverrors.WrapAndTrace(err) 45 | } 46 | 47 | ta := table.NewWriter() 48 | ta.SetOutputMirror(os.Stdout) 49 | ta.Style().Options = getBrevTableOptions() 50 | header := table.Row{"NAME", "PLATFORM ID", "PLATFORM TYPE"} 51 | ta.AppendHeader(header) 52 | for _, w := range wsgs { 53 | workspaceRow := []table.Row{{ 54 | w.Name, w.PlatformID, w.Platform, 55 | }} 56 | ta.AppendRows(workspaceRow) 57 | } 58 | ta.Render() 59 | return nil 60 | } 61 | 62 | func getBrevTableOptions() table.Options { 63 | options := table.OptionsDefault 64 | options.DrawBorder = false 65 | options.SeparateColumns = false 66 | options.SeparateRows = false 67 | options.SeparateHeader = false 68 | return options 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/writeconnectionevent/writeconnectionevent.go: -------------------------------------------------------------------------------- 1 | package writeconnectionevent 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | ) 9 | 10 | var ( 11 | short = "TODO" 12 | long = "TODO" 13 | example = "TODO" 14 | ) 15 | 16 | type writeConnectionEventStore interface { 17 | WriteConnectionEvent() error 18 | } 19 | 20 | func NewCmdwriteConnectionEvent(t *terminal.Terminal, store writeConnectionEventStore) *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "write-connection-event", 23 | DisableFlagsInUseLine: true, 24 | Short: short, 25 | Long: long, 26 | Example: example, 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | err := RunWriteConnectionEvent(t, args, store) 29 | if err != nil { 30 | return breverrors.WrapAndTrace(err) 31 | } 32 | return nil 33 | }, 34 | } 35 | return cmd 36 | } 37 | 38 | func RunWriteConnectionEvent(_ *terminal.Terminal, _ []string, store writeConnectionEventStore) error { 39 | err := store.WriteConnectionEvent() 40 | if err != nil { 41 | return breverrors.WrapAndTrace(err) 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/cmd/writeconnectionevent/writeconnectionevent_test.go: -------------------------------------------------------------------------------- 1 | package writeconnectionevent 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brevdev/brev-cli/pkg/store" 7 | "github.com/brevdev/brev-cli/pkg/terminal" 8 | "github.com/spf13/afero" 9 | ) 10 | 11 | func TestRunWriteConnectionEvent(t *testing.T) { 12 | fs := afero.NewMemMapFs() 13 | type args struct { 14 | in0 *terminal.Terminal 15 | in1 []string 16 | store writeConnectionEventStore 17 | } 18 | tests := []struct { 19 | name string 20 | args args 21 | wantErr bool 22 | }{ 23 | // TODO: Add test cases. 24 | { 25 | name: "write connection event", 26 | args: args{ 27 | nil, 28 | []string{}, 29 | store.NewBasicStore().WithFileSystem(fs), 30 | }, 31 | wantErr: false, 32 | }, 33 | } 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | if err := RunWriteConnectionEvent(tt.args.in0, tt.args.in1, tt.args.store); (err != nil) != tt.wantErr { 37 | t.Errorf("RunWriteConnectionEvent() error = %v, wantErr %v", err, tt.wantErr) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/cmdcontext/cmdcontext.go: -------------------------------------------------------------------------------- 1 | package cmdcontext 2 | 3 | import ( 4 | breverrors "github.com/brevdev/brev-cli/pkg/errors" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // InvokeParentPersistentPreRun executes the immediate parent command's 9 | // PersistentPreRunE and PersistentPreRun functions, in that order. If 10 | // an error is returned from PersistentPreRunE, it is immediately returned. 11 | // 12 | // TODO: reverse walk up command tree? would need to ensure no one parent is invoked multiple times. 13 | func InvokeParentPersistentPreRun(cmd *cobra.Command, args []string) error { 14 | parentCmd := cmd.Parent() 15 | if parentCmd == nil { 16 | return nil 17 | } 18 | 19 | var err error 20 | 21 | // Invoke PersistentPreRunE, returning an error if one occurs 22 | // If no error is returned, proceed with PersistentPreRun 23 | parentPersistentPreRunE := parentCmd.PersistentPreRunE 24 | if parentPersistentPreRunE != nil { 25 | err = parentPersistentPreRunE(parentCmd, args) 26 | } 27 | if err != nil { 28 | return breverrors.WrapAndTrace(err) 29 | } 30 | 31 | // Invoke PersistentPreRun 32 | parentPersistentPreRun := parentCmd.PersistentPreRun 33 | if parentPersistentPreRun != nil { 34 | parentPersistentPreRun(parentCmd, args) 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cmdcontext/cmdwriter.go: -------------------------------------------------------------------------------- 1 | package cmdcontext 2 | 3 | // NoopWriter is an implementation of the standard Writer which takes no action 4 | // upon being asked to write. 5 | type NoopWriter struct{} 6 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type EnvVarName string // should be caps with underscore 8 | 9 | const ( 10 | brevAPIURL EnvVarName = "BREV_API_URL" 11 | coordURL EnvVarName = "BREV_COORD_URL" 12 | version EnvVarName = "VERSION" 13 | clusterID EnvVarName = "DEFAULT_CLUSTER_ID" 14 | defaultWorkspaceClass EnvVarName = "DEFAULT_WORKSPACE_CLASS" 15 | defaultWorkspaceTemplate EnvVarName = "DEFAULT_WORKSPACE_TEMPLATE" 16 | sentryURL EnvVarName = "DEFAULT_SENTRY_URL" 17 | debugHTTP EnvVarName = "DEBUG_HTTP" 18 | ollamaAPIURL EnvVarName = "OLLAMA_API_URL" 19 | ) 20 | 21 | var ConsoleBaseURL = "https://console.brev.dev" 22 | 23 | type ConstantsConfig struct{} 24 | 25 | func NewConstants() *ConstantsConfig { 26 | return &ConstantsConfig{} 27 | } 28 | 29 | func (c ConstantsConfig) GetBrevAPIURl() string { 30 | return getEnvOrDefault(brevAPIURL, "https://brevapi.us-west-2-prod.control-plane.brev.dev") 31 | } 32 | 33 | func (c ConstantsConfig) GetOllamaAPIURL() string { 34 | return getEnvOrDefault(ollamaAPIURL, "https://registry.ollama.ai") 35 | } 36 | 37 | func (c ConstantsConfig) GetDefaultClusterID() string { 38 | return getEnvOrDefault(clusterID, "devplane-brev-1") 39 | } 40 | 41 | func (c ConstantsConfig) GetDefaultWorkspaceClass() string { 42 | return getEnvOrDefault(defaultWorkspaceClass, "") 43 | } 44 | 45 | func (c ConstantsConfig) GetDefaultWorkspaceTemplate() string { 46 | // "test-template-aws" 47 | return getEnvOrDefault(defaultWorkspaceTemplate, "") 48 | } 49 | 50 | func (c ConstantsConfig) GetDebugHTTP() bool { 51 | return getEnvOrDefault(debugHTTP, "") != "" 52 | } 53 | 54 | func getEnvOrDefault(envVarName EnvVarName, defaultVal string) string { 55 | val := os.Getenv(string(envVarName)) 56 | if val == "" { 57 | return defaultVal 58 | } 59 | return val 60 | } 61 | 62 | var GlobalConfig = NewConstants() 63 | 64 | type EnvVarConfig struct { 65 | ConstantsConfig 66 | } 67 | 68 | type FileConfig struct { 69 | EnvVarConfig 70 | } 71 | 72 | type FlagsConfig struct { 73 | FileConfig 74 | } 75 | 76 | type InitConfig interface{} 77 | 78 | type AllConfig interface { 79 | InitConfig 80 | GetBrevAPIURl() string 81 | GetVersion() string 82 | GetDefaultClusterID() string 83 | } 84 | -------------------------------------------------------------------------------- /pkg/entity/generic/entitygeneric.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/entity" 5 | orderedmap "github.com/wk8/go-ordered-map/v2" 6 | ) 7 | 8 | func MakeVirtualProjectMap() *orderedmap.OrderedMap[string, map[string][]entity.Workspace] { 9 | vpMap := orderedmap.New[string, map[string][]entity.Workspace]() 10 | return vpMap 11 | } 12 | -------------------------------------------------------------------------------- /pkg/entity/virtualproject/virtualproject.go: -------------------------------------------------------------------------------- 1 | package virtualproject 2 | 3 | import ( 4 | "github.com/brevdev/brev-cli/pkg/entity" 5 | "github.com/brevdev/brev-cli/pkg/entity/generic" 6 | ) 7 | 8 | type VirtualProject struct { 9 | Name string 10 | GitURL string 11 | WorkspacesByUser map[string][]entity.Workspace 12 | } 13 | 14 | func NewVirtualProjects(workspaces []entity.Workspace) []VirtualProject { 15 | gitRepoWorkspaceMap := generic.MakeVirtualProjectMap() 16 | for _, w := range workspaces { 17 | if _, ok := gitRepoWorkspaceMap.Get(w.GitRepo); !ok { 18 | gitRepoWorkspaceMap.Set(w.GitRepo, make(map[string][]entity.Workspace)) 19 | } 20 | m, ok := gitRepoWorkspaceMap.Get(w.GitRepo) // [w.GitRepo][w.CreatedByUserID] = 21 | if !ok { 22 | panic("no") 23 | } 24 | m[w.CreatedByUserID] = append(m[w.CreatedByUserID], w) 25 | gitRepoWorkspaceMap.Set(w.GitRepo, m) 26 | } 27 | var projects []VirtualProject 28 | for pair := gitRepoWorkspaceMap.Oldest(); pair != nil; pair = pair.Next() { 29 | key, ok := GetFirstKeyMap(pair.Value) 30 | if !ok { 31 | continue 32 | } 33 | 34 | if len(pair.Value[key]) == 0 { 35 | continue 36 | } 37 | 38 | projectName := pair.Value[key][0].Name // TODO this is the SUPER hacky unexpected behavior part 39 | projects = append(projects, VirtualProject{Name: projectName, GitURL: pair.Key, WorkspacesByUser: pair.Value}) 40 | } 41 | return projects 42 | } 43 | 44 | func GetFirstKeyMap(strMap map[string][]entity.Workspace) (string, bool) { 45 | for k := range strMap { 46 | return k, true 47 | } 48 | return "", false 49 | } 50 | 51 | func (v VirtualProject) GetUserWorkspaces(userID string) []entity.Workspace { 52 | return v.WorkspacesByUser[userID] 53 | } 54 | 55 | func (v VirtualProject) GetUniqueUserCount() int { 56 | return len(v.WorkspacesByUser) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/entity/virtualproject/virtualproject_test.go: -------------------------------------------------------------------------------- 1 | package virtualproject 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brevdev/brev-cli/pkg/entity" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewVirtualProjectOne(t *testing.T) { 11 | ps := NewVirtualProjects([]entity.Workspace{{ 12 | ID: "1", 13 | Name: "hi", 14 | GitRepo: "git://hi", 15 | CreatedByUserID: "me", 16 | }}) 17 | if !assert.Len(t, ps, 1) { 18 | return 19 | } 20 | assert.Equal(t, ps[0].Name, "hi") 21 | assert.Equal(t, ps[0].GitURL, "git://hi") 22 | assert.Equal(t, 1, ps[0].GetUniqueUserCount()) 23 | assert.Len(t, ps[0].GetUserWorkspaces("me"), 1) 24 | } 25 | 26 | func TestNewVirtualProjectNone(t *testing.T) { 27 | ps := NewVirtualProjects([]entity.Workspace{}) 28 | assert.Len(t, ps, 0) 29 | } 30 | 31 | func TestNewVirtualProjectTwoDiffRepo(t *testing.T) { 32 | ps := NewVirtualProjects([]entity.Workspace{{ 33 | ID: "1", 34 | Name: "hi", 35 | GitRepo: "git://hi", 36 | CreatedByUserID: "me", 37 | }, { 38 | ID: "2", 39 | Name: "bye", 40 | GitRepo: "git://bye", 41 | CreatedByUserID: "other", 42 | }}) 43 | if !assert.Len(t, ps, 2) { 44 | return 45 | } 46 | assert.Equal(t, ps[0].Name, "hi") 47 | assert.Equal(t, ps[0].GitURL, "git://hi") 48 | assert.Equal(t, 1, ps[0].GetUniqueUserCount()) 49 | assert.Len(t, ps[0].GetUserWorkspaces("me"), 1) 50 | 51 | assert.Equal(t, ps[1].Name, "bye") 52 | assert.Equal(t, ps[1].GitURL, "git://bye") 53 | assert.Equal(t, 1, ps[1].GetUniqueUserCount()) 54 | assert.Len(t, ps[1].GetUserWorkspaces("other"), 1) 55 | } 56 | 57 | func TestNewVirtualProjectTwoSameRepo(t *testing.T) { 58 | ps := NewVirtualProjects([]entity.Workspace{{ 59 | ID: "1", 60 | Name: "hi", 61 | GitRepo: "git://hi", 62 | CreatedByUserID: "me", 63 | }, { 64 | ID: "2", 65 | Name: "hi", 66 | GitRepo: "git://hi", 67 | CreatedByUserID: "other", 68 | }}) 69 | if !assert.Len(t, ps, 1) { 70 | return 71 | } 72 | assert.Equal(t, ps[0].Name, "hi") 73 | assert.Equal(t, ps[0].GitURL, "git://hi") 74 | assert.Equal(t, 2, ps[0].GetUniqueUserCount()) 75 | assert.Len(t, ps[0].GetUserWorkspaces("me"), 1) 76 | assert.Len(t, ps[0].GetUserWorkspaces("other"), 1) 77 | } 78 | 79 | func TestNewVirtualProjectTwoSameRepoSameUser(t *testing.T) { 80 | ps := NewVirtualProjects([]entity.Workspace{{ 81 | ID: "1", 82 | Name: "hi", 83 | GitRepo: "git://hi", 84 | CreatedByUserID: "me", 85 | }, { 86 | ID: "2", 87 | Name: "hi", 88 | GitRepo: "git://hi", 89 | CreatedByUserID: "me", 90 | }}) 91 | if !assert.Len(t, ps, 1) { 92 | return 93 | } 94 | assert.Equal(t, ps[0].Name, "hi") 95 | assert.Equal(t, ps[0].GitURL, "git://hi") 96 | assert.Equal(t, 1, ps[0].GetUniqueUserCount()) 97 | assert.Len(t, ps[0].GetUserWorkspaces("me"), 2) 98 | assert.Len(t, ps[0].GetUserWorkspaces("other"), 0) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/featureflag/featureflag.go: -------------------------------------------------------------------------------- 1 | package featureflag 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/brevdev/brev-cli/pkg/cmd/version" 7 | "github.com/brevdev/brev-cli/pkg/entity" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func IsDev() bool { 12 | if viper.IsSet("feature.dev") { 13 | return viper.GetBool("feature.dev") 14 | } else { 15 | return strings.HasPrefix(version.Version, "dev") 16 | } 17 | } 18 | 19 | func IsAdmin(userType entity.GlobalUserType) bool { 20 | if viper.IsSet("feature.not_admin") && viper.GetBool("feature.not_admin") { 21 | return false 22 | } else { 23 | return userType == "Admin" 24 | } 25 | } 26 | 27 | // use feature flag if not provided default true for admin but not others 28 | 29 | func DisableSSHProxyVersionCheck() bool { 30 | return viper.GetBool("feature.disable_ssh_proxy_version_check") 31 | } 32 | 33 | func ShowVersionOnRun() bool { 34 | return viper.GetBool("feature.show_version_on_run") 35 | } 36 | 37 | // todo set me via cli flag? this was meant to sort of like verbose but could be 38 | // removed in favor of something like that 39 | func Debug() bool { 40 | return viper.GetBool("feature.debug") 41 | } 42 | 43 | func LoadFeatureFlags(path string) error { 44 | viper.SetConfigName("config") 45 | viper.AddConfigPath("/etc/brev/") 46 | viper.AddConfigPath(path) 47 | viper.SetEnvPrefix("brev") 48 | viper.SetConfigType("yaml") 49 | viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 50 | viper.AutomaticEnv() 51 | 52 | _ = viper.ReadInConfig() // do not need to fail if can't find config file 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/featureflag/featureflag_test.go: -------------------------------------------------------------------------------- 1 | package featureflag 2 | -------------------------------------------------------------------------------- /pkg/files/files_test.go: -------------------------------------------------------------------------------- 1 | package files 2 | 3 | // Basic imports 4 | import ( 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | // Define the suite, and absorb the built-in basic suite 12 | // functionality from testify - including a T() method which 13 | // returns the current testing context 14 | type filesTestSuite struct { 15 | suite.Suite 16 | } 17 | 18 | // All methods that begin with "Test" are run as tests within a 19 | // suite. 20 | func (s *filesTestSuite) TestGetUserSSHConfigPath() { 21 | home, _ := os.UserHomeDir() 22 | _, err := GetUserSSHConfigPath(home) 23 | s.Nil(err) 24 | } 25 | 26 | // In order for 'go test' to run this suite, we need to create 27 | // a normal test function and pass our suite to suite.Run 28 | func TestFiles(t *testing.T) { 29 | suite.Run(t, new(filesTestSuite)) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/huproxyclient/huproxyclient.go: -------------------------------------------------------------------------------- 1 | package huproxyclient 2 | 3 | // https://github.com/google/huproxy/blob/master/huproxyclient/client.go 4 | 5 | import ( 6 | "context" 7 | "crypto/tls" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "os" 13 | "time" 14 | 15 | "github.com/brevdev/brev-cli/pkg/entity" 16 | "github.com/brevdev/brev-cli/pkg/errors" 17 | "github.com/gorilla/websocket" 18 | log "github.com/sirupsen/logrus" 19 | 20 | huproxy "github.com/google/huproxy/lib" 21 | ) 22 | 23 | var writeTimeout = 10 * time.Second 24 | 25 | type HubProxyStore interface { 26 | GetAuthTokens() (*entity.AuthTokens, error) 27 | GetCurrentWorkspaceGroupID() (string, error) 28 | } 29 | 30 | func dialError(url string, resp *http.Response, err error) { 31 | if resp != nil { 32 | extra := "" 33 | b, err1 := ioutil.ReadAll(resp.Body) 34 | if err1 != nil { 35 | log.Warningf("Failed to read HTTP body: %v", err1) 36 | } 37 | extra = "Body:\n" + string(b) 38 | log.Fatalf("%s: HTTP error: %d %s\n%s", err, resp.StatusCode, resp.Status, extra) 39 | 40 | } 41 | log.Fatalf("Dial to %q fail: %v", url, err) 42 | } 43 | 44 | func Run(url string, store HubProxyStore) error { 45 | ctx, cancel := context.WithCancel(context.Background()) 46 | defer cancel() 47 | 48 | dialer := websocket.Dialer{} 49 | dialer.TLSClientConfig = new(tls.Config) 50 | 51 | head := map[string][]string{} 52 | 53 | token, err := store.GetAuthTokens() 54 | if err != nil { 55 | return errors.WrapAndTrace(err) 56 | } 57 | 58 | workspaceGroupID, err := store.GetCurrentWorkspaceGroupID() 59 | if err != nil { 60 | fmt.Printf("%v\n", err) 61 | } 62 | if workspaceGroupID != "" { 63 | head["X-Workspace-Group-ID"] = []string{workspaceGroupID} 64 | } 65 | 66 | head["Authorization"] = []string{ 67 | "Bearer " + token.AccessToken, 68 | } 69 | 70 | conn, resp, err := dialer.Dial(url, head) 71 | if err != nil { 72 | dialError(url, resp, err) 73 | } 74 | defer resp.Body.Close() //nolint:errcheck // lazy to refactor 75 | defer conn.Close() //nolint:errcheck // lazy to refactor 76 | 77 | RunProxy(ctx, conn, cancel) 78 | 79 | if ctx.Err() != nil { 80 | return errors.WrapAndTrace(ctx.Err()) 81 | } 82 | return nil 83 | } 84 | 85 | func RunProxy(ctx context.Context, conn *websocket.Conn, cancel context.CancelFunc) { 86 | // websocket -> stdout 87 | go func() { 88 | for { 89 | mt, r, err := conn.NextReader() 90 | if websocket.IsCloseError(err, websocket.CloseNormalClosure) { 91 | return 92 | } 93 | if err != nil { 94 | log.Warn("Workspace disconnect: may be from network failure or workspace was stopped/deleted") 95 | log.Fatal(err) 96 | } 97 | if mt != websocket.BinaryMessage { 98 | log.Fatal("non-binary websocket message received") 99 | } 100 | if _, err := io.Copy(os.Stdout, r); err != nil { 101 | log.Errorf("Reading from websocket: %v", err) 102 | cancel() 103 | } 104 | } 105 | }() 106 | 107 | // stdin -> websocket 108 | // TODO: NextWriter() seems to be broken. 109 | if err := huproxy.File2WS(ctx, cancel, os.Stdin, conn); err == io.EOF { 110 | if err1 := conn.WriteControl(websocket.CloseMessage, 111 | websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), 112 | time.Now().Add(writeTimeout)); err1 == websocket.ErrCloseSent { 113 | _ = "" 114 | } else if err1 != nil { 115 | log.Errorf("Error sending 'close' message: %v", err1) 116 | } 117 | } else if err != nil { 118 | log.Errorf("reading from stdin: %v", err) 119 | cancel() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pkg/huproxyclient/huproxyclient_test.go: -------------------------------------------------------------------------------- 1 | package huproxyclient 2 | -------------------------------------------------------------------------------- /pkg/ids/ids.go: -------------------------------------------------------------------------------- 1 | package ids 2 | 3 | import "github.com/brevdev/brev-cli/pkg/prefixid" 4 | 5 | type ( 6 | CloudCredID prefixid.PrefixID 7 | InstanceID prefixid.PrefixID 8 | CloudProviderInstanceID prefixid.PrefixID 9 | CloudProviderID string 10 | ) 11 | 12 | type HealthCheckID prefixid.PrefixID 13 | 14 | type CreditID prefixid.PrefixID 15 | 16 | type LimitID prefixid.PrefixID 17 | -------------------------------------------------------------------------------- /pkg/mergeshells/templates/gatsby/gatsby: -------------------------------------------------------------------------------- 1 | # gatsby 2 | # dependencies: node npm-no-sudo 3 | # installing gatsby-cli 4 | npm install -g gatsby-cli 5 | -------------------------------------------------------------------------------- /pkg/mergeshells/templates/golang/golang: -------------------------------------------------------------------------------- 1 | # golang 2 | # installing Golang 3 | (echo ""; echo "##### Golang ${version} #####"; echo "";) 4 | wget https://golang.org/dl/go${version}.linux-amd64.tar.gz 5 | sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go${version}.linux-amd64.tar.gz 6 | echo "" | sudo tee -a ~/.bashrc 7 | echo "export PATH=\$PATH:/usr/local/go/bin" | sudo tee -a ~/.bashrc 8 | echo "export PATH=\$PATH:$HOME/go/bin" | sudo tee -a ~/.bashrc 9 | source ~/.bashrc 10 | echo "" | sudo tee -a ~/.zshrc 11 | echo "export PATH=\$PATH:/usr/local/go/bin" | sudo tee -a ~/.zshrc 12 | echo "export PATH=\$PATH:$HOME/go/bin" | sudo tee -a ~/.zshrc 13 | source ~/.zshrc 14 | rm go${version}.linux-amd64.tar.gz 15 | 16 | -------------------------------------------------------------------------------- /pkg/mergeshells/templates/node/14: -------------------------------------------------------------------------------- 1 | # node node 2 | # installing Node v14.x + npm 3 | (echo ""; echo "##### Node v14.x + npm #####"; echo "";) 4 | sudo apt install ca-certificates 5 | curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - 6 | sudo apt-get install -y nodejs 7 | (echo ""; echo "##### Node v14.x + npm #####"; echo "";) 8 | sudo apt install ca-certificates 9 | curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash - 10 | sudo apt-get install -y nodejs 11 | 12 | # npm-no-sudo 13 | # dependencies: node 14 | # installing npm packages globally without sudo | modified from https://stackoverflow.com/questions/18088372/how-to-npm-install-global-not-as-root 15 | mkdir "${HOME}/.npm-packages" 16 | printf "prefix=${HOME}/.npm-packages" >> $HOME/.npmrc 17 | cat <> $HOME/.npmrc 17 | cat < { inherit system; } }: 2 | 3 | with pkgs; 4 | let 5 | pkgs = import (builtins.fetchGit { 6 | # Descriptive name to make the store path easier to identify 7 | name = "my-old-revision"; 8 | url = "https://github.com/NixOS/nixpkgs/"; 9 | ref = "refs/heads/nixpkgs-unstable"; 10 | rev = "ff8b619cfecb98bb94ae49ca7ceca937923a75fa"; 11 | # }) {}; 12 | }) { 13 | inherit system; 14 | }; 15 | flake-utils = { 16 | url = "github:numtide/flake-utils"; 17 | inputs.nixpkgs.follows = "nixpkgs"; 18 | }; 19 | olderVersionOfGolangci-lint = pkgs.golangci-lint; 20 | # system = builtins.currentSystem; 21 | in 22 | mkShell { 23 | nativeBuildInputs = [ 24 | go_1_19 25 | gopls 26 | tmux 27 | gofumpt 28 | olderVersionOfGolangci-lint 29 | gosec 30 | delve 31 | go-tools 32 | gotests 33 | gomodifytags 34 | ]; 35 | # pkgs.system="x86_64-linux"; 36 | } 37 | # ) -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | // Manage tool dependencies via go.mod. 7 | // 8 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 9 | // https://github.com/golang/go/issues/25922 10 | import ( 11 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 12 | _ "github.com/goreleaser/goreleaser" 13 | _ "mvdan.cc/gofumpt" 14 | ) 15 | --------------------------------------------------------------------------------