diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 36397b7d..d7712b14 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -44,3 +44,17 @@ jobs: uses: ./ci/image with: args: go test ./internal/... ./cmd/... + gendocs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: generate-docs + uses: ./ci/image + with: + args: ./ci/steps/gendocs.sh diff --git a/README.md b/README.md index 427ad846..95b321a4 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ `coder` is a command line utility for Coder Enterprise. -To view usage documentation, head over to [https://enterprise.coder.com](https://enterprise.coder.com/docs/getting-started). - To report bugs and request features, please [open an issue](https://github.com/cdr/coder-cli/issues/new). +## Usage + +View the `coder-cli` documentation [here](./docs/coder.md). + +You can find additional Coder Enterprise usage documentation on [https://enterprise.coder.com](https://enterprise.coder.com/docs/getting-started). + ## Install Release Download the latest [release](https://github.com/cdr/coder-cli/releases): diff --git a/ci/integration/devurls_test.go b/ci/integration/devurls_test.go index 09f1c0e4..06553107 100644 --- a/ci/integration/devurls_test.go +++ b/ci/integration/devurls_test.go @@ -3,36 +3,23 @@ package integration import ( "context" "testing" - "time" "cdr.dev/coder-cli/ci/tcli" - "cdr.dev/slog/sloggers/slogtest/assert" ) func TestDevURLCLI(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) - defer cancel() - - c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ - Image: "codercom/enterprise-dev", - Name: "coder-cli-devurl-tests", - BindMounts: map[string]string{ - binpath: "/bin/coder", - }, + run(t, "coder-cli-devurl-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + c.Run(ctx, "which coder").Assert(t, + tcli.Success(), + tcli.StdoutMatches("/usr/sbin/coder"), + tcli.StderrEmpty(), + ) + + c.Run(ctx, "coder urls ls").Assert(t, + tcli.Error(), + ) }) - assert.Success(t, "new run container", err) - defer c.Close() - - c.Run(ctx, "which coder").Assert(t, - tcli.Success(), - tcli.StdoutMatches("/usr/sbin/coder"), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder urls ls").Assert(t, - tcli.Error(), - ) // The following cannot be enabled nor verified until either the // integration testing dogfood target has environments created, or diff --git a/ci/integration/integration_test.go b/ci/integration/integration_test.go index 77a4ec91..eccf68a2 100644 --- a/ci/integration/integration_test.go +++ b/ci/integration/integration_test.go @@ -10,67 +10,78 @@ import ( "cdr.dev/slog/sloggers/slogtest/assert" ) +func run(t *testing.T, container string, execute func(t *testing.T, ctx context.Context, runner *tcli.ContainerRunner)) { + t.Run(container, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + defer cancel() + + c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ + Image: "codercom/enterprise-dev", + Name: container, + BindMounts: map[string]string{ + binpath: "/bin/coder", + }, + }) + assert.Success(t, "new run container", err) + defer c.Close() + + execute(t, ctx, c) + }) +} + func TestCoderCLI(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) - defer cancel() - - c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ - Image: "codercom/enterprise-dev", - Name: "coder-cli-tests", - BindMounts: map[string]string{ - binpath: "/bin/coder", - }, + run(t, "test-coder-cli", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + c.Run(ctx, "which coder").Assert(t, + tcli.Success(), + tcli.StdoutMatches("/usr/sbin/coder"), + tcli.StderrEmpty(), + ) + + c.Run(ctx, "coder --version").Assert(t, + tcli.StderrEmpty(), + tcli.Success(), + tcli.StdoutMatches("linux"), + ) + + c.Run(ctx, "coder --help").Assert(t, + tcli.Success(), + tcli.StdoutMatches("Available Commands"), + ) + + headlessLogin(ctx, t, c) + + c.Run(ctx, "coder envs").Assert(t, + tcli.Success(), + ) + + c.Run(ctx, "coder envs ls").Assert(t, + tcli.Success(), + ) + + c.Run(ctx, "coder urls").Assert(t, + tcli.Success(), + ) + + c.Run(ctx, "coder sync").Assert(t, + tcli.Error(), + ) + + c.Run(ctx, "coder sh").Assert(t, + tcli.Error(), + ) + + c.Run(ctx, "coder logout").Assert(t, + tcli.Success(), + ) + + c.Run(ctx, "coder envs ls").Assert(t, + tcli.Error(), + ) }) - assert.Success(t, "new run container", err) - defer c.Close() - - c.Run(ctx, "which coder").Assert(t, - tcli.Success(), - tcli.StdoutMatches("/usr/sbin/coder"), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder --version").Assert(t, - tcli.StderrEmpty(), - tcli.Success(), - tcli.StdoutMatches("linux"), - ) - - c.Run(ctx, "coder --help").Assert(t, - tcli.Success(), - tcli.StdoutMatches("Available Commands"), - ) - - headlessLogin(ctx, t, c) - - c.Run(ctx, "coder envs").Assert(t, - tcli.Success(), - ) - - c.Run(ctx, "coder envs ls").Assert(t, - tcli.Success(), - ) - - c.Run(ctx, "coder urls").Assert(t, - tcli.Success(), - ) - - c.Run(ctx, "coder sync").Assert(t, - tcli.Error(), - ) - - c.Run(ctx, "coder sh").Assert(t, - tcli.Error(), - ) - - c.Run(ctx, "coder logout").Assert(t, - tcli.Success(), - ) - - c.Run(ctx, "coder envs ls").Assert(t, - tcli.Error(), - ) + } var seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) diff --git a/ci/integration/secrets_test.go b/ci/integration/secrets_test.go index dc063b9b..fadcc84e 100644 --- a/ci/integration/secrets_test.go +++ b/ci/integration/secrets_test.go @@ -5,91 +5,77 @@ import ( "fmt" "regexp" "testing" - "time" "cdr.dev/coder-cli/ci/tcli" - "cdr.dev/slog/sloggers/slogtest/assert" ) func TestSecrets(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) - defer cancel() - - c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ - Image: "codercom/enterprise-dev", - Name: "secrets-cli-tests", - BindMounts: map[string]string{ - binpath: "/bin/coder", - }, + run(t, "secrets-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + headlessLogin(ctx, t, c) + + c.Run(ctx, "coder secrets ls").Assert(t, + tcli.Success(), + ) + + name, value := randString(8), randString(8) + + c.Run(ctx, "coder secrets create").Assert(t, + tcli.Error(), + ) + + // this tests the "Value:" prompt fallback + c.Run(ctx, fmt.Sprintf("echo %s | coder secrets create %s --from-prompt", value, name)).Assert(t, + tcli.Success(), + tcli.StderrEmpty(), + ) + + c.Run(ctx, "coder secrets ls").Assert(t, + tcli.Success(), + tcli.StderrEmpty(), + tcli.StdoutMatches("Value"), + tcli.StdoutMatches(regexp.QuoteMeta(name)), + ) + + c.Run(ctx, "coder secrets view "+name).Assert(t, + tcli.Success(), + tcli.StderrEmpty(), + tcli.StdoutMatches(regexp.QuoteMeta(value)), + ) + + c.Run(ctx, "coder secrets rm").Assert(t, + tcli.Error(), + ) + c.Run(ctx, "coder secrets rm "+name).Assert(t, + tcli.Success(), + ) + c.Run(ctx, "coder secrets view "+name).Assert(t, + tcli.Error(), + tcli.StdoutEmpty(), + ) + + name, value = randString(8), randString(8) + + c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s", name, value)).Assert(t, + tcli.Success(), + tcli.StderrEmpty(), + ) + + c.Run(ctx, "coder secrets view "+name).Assert(t, + tcli.Success(), + tcli.StdoutMatches(regexp.QuoteMeta(value)), + ) + + name, value = randString(8), randString(8) + c.Run(ctx, fmt.Sprintf("echo %s > ~/secret.json", value)).Assert(t, + tcli.Success(), + ) + c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-file ~/secret.json", name)).Assert(t, + tcli.Success(), + ) + c.Run(ctx, "coder secrets view "+name).Assert(t, + tcli.Success(), + tcli.StdoutMatches(regexp.QuoteMeta(value)), + ) }) - assert.Success(t, "new run container", err) - defer c.Close() - - headlessLogin(ctx, t, c) - - c.Run(ctx, "coder secrets ls").Assert(t, - tcli.Success(), - ) - - name, value := randString(8), randString(8) - - c.Run(ctx, "coder secrets create").Assert(t, - tcli.Error(), - ) - - // this tests the "Value:" prompt fallback - c.Run(ctx, fmt.Sprintf("echo %s | coder secrets create %s --from-prompt", value, name)).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder secrets ls").Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - tcli.StdoutMatches("Value"), - tcli.StdoutMatches(regexp.QuoteMeta(name)), - ) - - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) - - c.Run(ctx, "coder secrets rm").Assert(t, - tcli.Error(), - ) - c.Run(ctx, "coder secrets rm "+name).Assert(t, - tcli.Success(), - ) - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Error(), - tcli.StdoutEmpty(), - ) - - name, value = randString(8), randString(8) - - c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s", name, value)).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) - - name, value = randString(8), randString(8) - c.Run(ctx, fmt.Sprintf("echo %s > ~/secret.json", value)).Assert(t, - tcli.Success(), - ) - c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-file ~/secret.json", name)).Assert(t, - tcli.Success(), - ) - // - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) } diff --git a/ci/integration/ssh_test.go b/ci/integration/ssh_test.go new file mode 100644 index 00000000..273e3850 --- /dev/null +++ b/ci/integration/ssh_test.go @@ -0,0 +1,18 @@ +package integration + +import ( + "context" + "testing" + + "cdr.dev/coder-cli/ci/tcli" +) + +func TestSSH(t *testing.T) { + t.Parallel() + run(t, "ssh-coder-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + headlessLogin(ctx, t, c) + c.Run(ctx, "coder config-ssh").Assert(t, + tcli.Success(), + ) + }) +} diff --git a/ci/integration/users_test.go b/ci/integration/users_test.go index 659ccc7a..e3c7e6f3 100644 --- a/ci/integration/users_test.go +++ b/ci/integration/users_test.go @@ -3,7 +3,6 @@ package integration import ( "context" "testing" - "time" "cdr.dev/coder-cli/ci/tcli" "cdr.dev/coder-cli/internal/entclient" @@ -12,45 +11,34 @@ import ( func TestUsers(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) - defer cancel() - - c, err := tcli.NewContainerRunner(ctx, &tcli.ContainerConfig{ - Image: "codercom/enterprise-dev", - Name: "users-cli-tests", - BindMounts: map[string]string{ - binpath: "/bin/coder", - }, + run(t, "users-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + c.Run(ctx, "which coder").Assert(t, + tcli.Success(), + tcli.StdoutMatches("/usr/sbin/coder"), + tcli.StderrEmpty(), + ) + + headlessLogin(ctx, t, c) + + var user entclient.User + c.Run(ctx, `coder users ls --output json | jq -c '.[] | select( .username == "charlie")'`).Assert(t, + tcli.Success(), + tcli.StdoutJSONUnmarshal(&user), + ) + assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email) + assert.Equal(t, "username is as expected", "Charlie", user.Name) + + c.Run(ctx, "coder users ls --output human | grep charlie").Assert(t, + tcli.Success(), + tcli.StdoutMatches("charlie"), + ) + + c.Run(ctx, "coder logout").Assert(t, + tcli.Success(), + ) + + c.Run(ctx, "coder users ls").Assert(t, + tcli.Error(), + ) }) - assert.Success(t, "new run container", err) - defer c.Close() - - c.Run(ctx, "which coder").Assert(t, - tcli.Success(), - tcli.StdoutMatches("/usr/sbin/coder"), - tcli.StderrEmpty(), - ) - - headlessLogin(ctx, t, c) - - var user entclient.User - c.Run(ctx, `coder users ls --output json | jq -c '.[] | select( .username == "charlie")'`).Assert(t, - tcli.Success(), - tcli.StdoutJSONUnmarshal(&user), - ) - assert.Equal(t, "user email is as expected", "charlie@coder.com", user.Email) - assert.Equal(t, "username is as expected", "Charlie", user.Name) - - c.Run(ctx, "coder users ls --output human | grep charlie").Assert(t, - tcli.Success(), - tcli.StdoutMatches("charlie"), - ) - - c.Run(ctx, "coder logout").Assert(t, - tcli.Success(), - ) - - c.Run(ctx, "coder users ls").Assert(t, - tcli.Error(), - ) } diff --git a/ci/steps/build.sh b/ci/steps/build.sh index 8411ccde..8c3a2326 100755 --- a/ci/steps/build.sh +++ b/ci/steps/build.sh @@ -21,7 +21,8 @@ build(){ tar -czf "$tarname" coder popd - cp "$tmpdir/$tarname" ../bin + mkdir -p ../bin + cp "$tmpdir/$tarname" ../bin/$tarname rm -rf "$tmpdir" } diff --git a/ci/steps/fmt.sh b/ci/steps/fmt.sh index bb4b0d2c..c202dab9 100755 --- a/ci/steps/fmt.sh +++ b/ci/steps/fmt.sh @@ -1,16 +1,17 @@ #!/bin/bash + +set -euo pipefail + echo "Formatting..." go mod tidy gofmt -w -s . goimports -w "-local=$$(go list -m)" . -if [ "$CI" != "" ]; then - if [[ $(git ls-files --other --modified --exclude-standard) != "" ]]; then +if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then echo "Files need generation or are formatted incorrectly:" git -c color.ui=always status | grep --color=no '\e\[31m' echo "Please run the following locally:" echo " ./ci/steps/fmt.sh" exit 1 fi -fi \ No newline at end of file diff --git a/ci/steps/gendocs.sh b/ci/steps/gendocs.sh new file mode 100755 index 00000000..64a3776a --- /dev/null +++ b/ci/steps/gendocs.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -euo pipefail + +echo "Generating docs..." + +cd "$(dirname "$0")" +cd ../../ + +go run ./cmd/coder gen-docs ./docs + +# remove cobra footer from each file +for filename in ./docs/*.md; do + trimmed=$(head -n -1 "$filename") + echo "$trimmed" > $filename +done + + +if [[ ${CI-} && $(git ls-files --other --modified --exclude-standard) ]]; then + echo "Documentation needs generation:" + git -c color.ui=always status | grep --color=no '\e\[31m' + echo "Please run the following locally:" + echo " ./ci/steps/gendocs.sh" + exit 1 +fi diff --git a/ci/steps/lint.sh b/ci/steps/lint.sh index 51da081d..c3f72614 100755 --- a/ci/steps/lint.sh +++ b/ci/steps/lint.sh @@ -1,6 +1,8 @@ #!/bin/bash +set -euo pipefail + echo "Linting..." go vet ./... -golint -set_exit_status ./... \ No newline at end of file +golint -set_exit_status ./... diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 5cf3454b..5ed4344b 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -9,8 +9,8 @@ import ( "os" "runtime" + "cdr.dev/coder-cli/internal/cmd" "cdr.dev/coder-cli/internal/x/xterminal" - "github.com/spf13/cobra" "go.coder.com/flog" ) @@ -35,78 +35,11 @@ func main() { } defer xterminal.Restore(os.Stdout.Fd(), stdoutState) - app := &cobra.Command{ - Use: "coder", - Short: "coder provides a CLI for working with an existing Coder Enterprise installation", - Version: fmt.Sprintf("%s %s %s/%s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH), - } + app := cmd.Make() + app.Version = fmt.Sprintf("%s %s %s/%s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) - app.AddCommand( - makeLoginCmd(), - makeLogoutCmd(), - makeShellCmd(), - makeUsersCmd(), - makeConfigSSHCmd(), - makeSecretsCmd(), - makeEnvsCommand(), - makeSyncCmd(), - makeURLCmd(), - completionCmd, - ) err = app.ExecuteContext(ctx) if err != nil { os.Exit(1) } } - -// reference: https://github.com/spf13/cobra/blob/master/shell_completions.md -var completionCmd = &cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", - Long: `To load completions: - -Bash: - -$ source <(yourprogram completion bash) - -# To load completions for each session, execute once: -Linux: - $ yourprogram completion bash > /etc/bash_completion.d/yourprogram -MacOS: - $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram - -Zsh: - -# If shell completion is not already enabled in your environment you will need -# to enable it. You can execute the following once: - -$ echo "autoload -U compinit; compinit" >> ~/.zshrc - -# To load completions for each session, execute once: -$ yourprogram completion zsh > "${fpath[1]}/_yourprogram" - -# You will need to start a new shell for this setup to take effect. - -Fish: - -$ yourprogram completion fish | source - -# To load completions for each session, execute once: -$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish -`, - DisableFlagsInUseLine: true, - ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { - switch args[0] { - case "bash": - cmd.Root().GenBashCompletion(os.Stdout) - case "zsh": - cmd.Root().GenZshCompletion(os.Stdout) - case "fish": - cmd.Root().GenFishCompletion(os.Stdout, true) - case "powershell": - cmd.Root().GenPowerShellCompletion(os.Stdout) - } - }, -} diff --git a/docs/coder.md b/docs/coder.md new file mode 100644 index 00000000..378a9c66 --- /dev/null +++ b/docs/coder.md @@ -0,0 +1,26 @@ +## coder + +coder provides a CLI for working with an existing Coder Enterprise installation + +### Synopsis + +coder provides a CLI for working with an existing Coder Enterprise installation + +### Options + +``` + -h, --help help for coder +``` + +### SEE ALSO + +* [coder completion](coder_completion.md) - Generate completion script +* [coder config-ssh](coder_config-ssh.md) - Configure SSH to access Coder environments +* [coder envs](coder_envs.md) - Interact with Coder environments +* [coder login](coder_login.md) - Authenticate this client for future operations +* [coder logout](coder_logout.md) - Remove local authentication credentials if any exist +* [coder secrets](coder_secrets.md) - Interact with Coder Secrets +* [coder sh](coder_sh.md) - Open a shell and execute commands in a Coder environment +* [coder sync](coder_sync.md) - Establish a one way directory sync to a Coder environment +* [coder urls](coder_urls.md) - Interact with environment DevURLs +* [coder users](coder_users.md) - Interact with Coder user accounts diff --git a/docs/coder_completion.md b/docs/coder_completion.md new file mode 100644 index 00000000..39b862af --- /dev/null +++ b/docs/coder_completion.md @@ -0,0 +1,63 @@ +## coder completion + +Generate completion script + +### Synopsis + +To load completions: + +Bash: + +$ source <(coder completion bash) + +To load completions for each session, execute once: +Linux: + $ coder completion bash > /etc/bash_completion.d/coder +MacOS: + $ coder completion bash > /usr/local/etc/bash_completion.d/coder + +Zsh: + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions for each session, execute once: +$ coder completion zsh > "${fpath[1]}/_coder" + +You will need to start a new shell for this setup to take effect. + +Fish: + +$ coder completion fish | source + +To load completions for each session, execute once: +$ coder completion fish > ~/.config/fish/completions/coder.fish + + +``` +coder completion [bash|zsh|fish|powershell] +``` + +### Examples + +``` +coder completion fish > ~/.config/fish/completions/coder.fish +coder completion zsh > "${fpath[1]}/_coder" + +Linux: + $ coder completion bash > /etc/bash_completion.d/coder +MacOS: + $ coder completion bash > /usr/local/etc/bash_completion.d/coder +``` + +### Options + +``` + -h, --help help for completion +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_config-ssh.md b/docs/coder_config-ssh.md new file mode 100644 index 00000000..41b697ef --- /dev/null +++ b/docs/coder_config-ssh.md @@ -0,0 +1,23 @@ +## coder config-ssh + +Configure SSH to access Coder environments + +### Synopsis + +Inject the proper OpenSSH configuration into your local SSH config file. + +``` +coder config-ssh [flags] +``` + +### Options + +``` + --filepath string overide the default path of your ssh config file (default "~/.ssh/config") + -h, --help help for config-ssh + --remove remove the auto-generated Coder Enterprise ssh config +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_envs.md b/docs/coder_envs.md new file mode 100644 index 00000000..a08c4b15 --- /dev/null +++ b/docs/coder_envs.md @@ -0,0 +1,19 @@ +## coder envs + +Interact with Coder environments + +### Synopsis + +Perform operations on the Coder environments owned by the active user. + +### Options + +``` + -h, --help help for envs + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation +* [coder envs ls](coder_envs_ls.md) - list all environments owned by the active user diff --git a/docs/coder_envs_ls.md b/docs/coder_envs_ls.md new file mode 100644 index 00000000..d3535af8 --- /dev/null +++ b/docs/coder_envs_ls.md @@ -0,0 +1,28 @@ +## coder envs ls + +list all environments owned by the active user + +### Synopsis + +List all Coder environments owned by the active user. + +``` +coder envs ls [flags] +``` + +### Options + +``` + -h, --help help for ls + -o, --output string human | json (default "human") +``` + +### Options inherited from parent commands + +``` + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder envs](coder_envs.md) - Interact with Coder environments diff --git a/docs/coder_login.md b/docs/coder_login.md new file mode 100644 index 00000000..9cd18ae8 --- /dev/null +++ b/docs/coder_login.md @@ -0,0 +1,21 @@ +## coder login + +Authenticate this client for future operations + +### Synopsis + +Authenticate this client for future operations + +``` +coder login [Coder Enterprise URL eg. http://my.coder.domain/] [flags] +``` + +### Options + +``` + -h, --help help for login +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_logout.md b/docs/coder_logout.md new file mode 100644 index 00000000..22bce303 --- /dev/null +++ b/docs/coder_logout.md @@ -0,0 +1,21 @@ +## coder logout + +Remove local authentication credentials if any exist + +### Synopsis + +Remove local authentication credentials if any exist + +``` +coder logout [flags] +``` + +### Options + +``` + -h, --help help for logout +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_secrets.md b/docs/coder_secrets.md new file mode 100644 index 00000000..ebdd1af2 --- /dev/null +++ b/docs/coder_secrets.md @@ -0,0 +1,22 @@ +## coder secrets + +Interact with Coder Secrets + +### Synopsis + +Interact with secrets objects owned by the active user. + +### Options + +``` + -h, --help help for secrets + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation +* [coder secrets create](coder_secrets_create.md) - Create a new secret +* [coder secrets ls](coder_secrets_ls.md) - List all secrets owned by the active user +* [coder secrets rm](coder_secrets_rm.md) - Remove one or more secrets by name +* [coder secrets view](coder_secrets_view.md) - View a secret by name diff --git a/docs/coder_secrets_create.md b/docs/coder_secrets_create.md new file mode 100644 index 00000000..c10a771e --- /dev/null +++ b/docs/coder_secrets_create.md @@ -0,0 +1,39 @@ +## coder secrets create + +Create a new secret + +### Synopsis + +Create a new secret object to store application secrets and access them securely from within your environments. + +``` +coder secrets create [secret_name] [flags] +``` + +### Examples + +``` +coder secrets create mysql-password --from-literal 123password +coder secrets create mysql-password --from-prompt +coder secrets create aws-credentials --from-file ./credentials.json +``` + +### Options + +``` + --description string a description of the secret + --from-file string a file from which to read the value of the secret + --from-literal string the value of the secret + --from-prompt enter the secret value through a terminal prompt + -h, --help help for create +``` + +### Options inherited from parent commands + +``` + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder secrets](coder_secrets.md) - Interact with Coder Secrets diff --git a/docs/coder_secrets_ls.md b/docs/coder_secrets_ls.md new file mode 100644 index 00000000..40408e8e --- /dev/null +++ b/docs/coder_secrets_ls.md @@ -0,0 +1,27 @@ +## coder secrets ls + +List all secrets owned by the active user + +### Synopsis + +List all secrets owned by the active user + +``` +coder secrets ls [flags] +``` + +### Options + +``` + -h, --help help for ls +``` + +### Options inherited from parent commands + +``` + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder secrets](coder_secrets.md) - Interact with Coder Secrets diff --git a/docs/coder_secrets_rm.md b/docs/coder_secrets_rm.md new file mode 100644 index 00000000..d58dc6f0 --- /dev/null +++ b/docs/coder_secrets_rm.md @@ -0,0 +1,33 @@ +## coder secrets rm + +Remove one or more secrets by name + +### Synopsis + +Remove one or more secrets by name + +``` +coder secrets rm [...secret_name] [flags] +``` + +### Examples + +``` +coder secrets rm mysql-password mysql-user +``` + +### Options + +``` + -h, --help help for rm +``` + +### Options inherited from parent commands + +``` + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder secrets](coder_secrets.md) - Interact with Coder Secrets diff --git a/docs/coder_secrets_view.md b/docs/coder_secrets_view.md new file mode 100644 index 00000000..e5a9770a --- /dev/null +++ b/docs/coder_secrets_view.md @@ -0,0 +1,33 @@ +## coder secrets view + +View a secret by name + +### Synopsis + +View a secret by name + +``` +coder secrets view [secret_name] [flags] +``` + +### Examples + +``` +coder secrets view mysql-password +``` + +### Options + +``` + -h, --help help for view +``` + +### Options inherited from parent commands + +``` + --user string Specify the user whose resources to target (default "me") +``` + +### SEE ALSO + +* [coder secrets](coder_secrets.md) - Interact with Coder Secrets diff --git a/docs/coder_sh.md b/docs/coder_sh.md new file mode 100644 index 00000000..6c88f203 --- /dev/null +++ b/docs/coder_sh.md @@ -0,0 +1,27 @@ +## coder sh + +Open a shell and execute commands in a Coder environment + +### Synopsis + +Execute a remote command on the environment\nIf no command is specified, the default shell is opened. + +``` +coder sh [environment_name] [] [flags] +``` + +### Examples + +``` +coder sh backend-env +``` + +### Options + +``` + -h, --help help for sh +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_sync.md b/docs/coder_sync.md new file mode 100644 index 00000000..03ca7a37 --- /dev/null +++ b/docs/coder_sync.md @@ -0,0 +1,22 @@ +## coder sync + +Establish a one way directory sync to a Coder environment + +### Synopsis + +Establish a one way directory sync to a Coder environment + +``` +coder sync [local directory] [:] [flags] +``` + +### Options + +``` + -h, --help help for sync + --init do initial transfer and exit +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation diff --git a/docs/coder_urls.md b/docs/coder_urls.md new file mode 100644 index 00000000..df4c3c70 --- /dev/null +++ b/docs/coder_urls.md @@ -0,0 +1,20 @@ +## coder urls + +Interact with environment DevURLs + +### Synopsis + +Interact with environment DevURLs + +### Options + +``` + -h, --help help for urls +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation +* [coder urls create](coder_urls_create.md) - Create a new devurl for an environment +* [coder urls ls](coder_urls_ls.md) - List all DevURLs for an environment +* [coder urls rm](coder_urls_rm.md) - Remove a dev url diff --git a/docs/coder_urls_create.md b/docs/coder_urls_create.md new file mode 100644 index 00000000..7afc8d8b --- /dev/null +++ b/docs/coder_urls_create.md @@ -0,0 +1,23 @@ +## coder urls create + +Create a new devurl for an environment + +### Synopsis + +Create a new devurl for an environment + +``` +coder urls create [env_name] [port] [--access ] [--name ] [flags] +``` + +### Options + +``` + --access string Set DevURL access to [private | org | authed | public] (default "private") + -h, --help help for create + --name string DevURL name +``` + +### SEE ALSO + +* [coder urls](coder_urls.md) - Interact with environment DevURLs diff --git a/docs/coder_urls_ls.md b/docs/coder_urls_ls.md new file mode 100644 index 00000000..1d01c2e5 --- /dev/null +++ b/docs/coder_urls_ls.md @@ -0,0 +1,22 @@ +## coder urls ls + +List all DevURLs for an environment + +### Synopsis + +List all DevURLs for an environment + +``` +coder urls ls [environment_name] [flags] +``` + +### Options + +``` + -h, --help help for ls + -o, --output string human|json (default "human") +``` + +### SEE ALSO + +* [coder urls](coder_urls.md) - Interact with environment DevURLs diff --git a/docs/coder_urls_rm.md b/docs/coder_urls_rm.md new file mode 100644 index 00000000..2b69e2bb --- /dev/null +++ b/docs/coder_urls_rm.md @@ -0,0 +1,21 @@ +## coder urls rm + +Remove a dev url + +### Synopsis + +Remove a dev url + +``` +coder urls rm [environment_name] [port] [flags] +``` + +### Options + +``` + -h, --help help for rm +``` + +### SEE ALSO + +* [coder urls](coder_urls.md) - Interact with environment DevURLs diff --git a/docs/coder_users.md b/docs/coder_users.md new file mode 100644 index 00000000..6482d76e --- /dev/null +++ b/docs/coder_users.md @@ -0,0 +1,18 @@ +## coder users + +Interact with Coder user accounts + +### Synopsis + +Interact with Coder user accounts + +### Options + +``` + -h, --help help for users +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation +* [coder users ls](coder_users_ls.md) - list all user accounts diff --git a/docs/coder_users_ls.md b/docs/coder_users_ls.md new file mode 100644 index 00000000..6cf7ccd1 --- /dev/null +++ b/docs/coder_users_ls.md @@ -0,0 +1,29 @@ +## coder users ls + +list all user accounts + +### Synopsis + +list all user accounts + +``` +coder users ls [flags] +``` + +### Examples + +``` +coder users ls -o json +coder users ls -o json | jq .[] | jq -r .email +``` + +### Options + +``` + -h, --help help for ls + -o, --output string human | json (default "human") +``` + +### SEE ALSO + +* [coder users](coder_users.md) - Interact with Coder user accounts diff --git a/cmd/coder/auth.go b/internal/cmd/auth.go similarity index 98% rename from cmd/coder/auth.go rename to internal/cmd/auth.go index abd0e8d0..64bcfb23 100644 --- a/cmd/coder/auth.go +++ b/internal/cmd/auth.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "net/url" diff --git a/cmd/coder/ceapi.go b/internal/cmd/ceapi.go similarity index 99% rename from cmd/coder/ceapi.go rename to internal/cmd/ceapi.go index 823f36ff..b69788fc 100644 --- a/cmd/coder/ceapi.go +++ b/internal/cmd/ceapi.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go new file mode 100644 index 00000000..30e4af7d --- /dev/null +++ b/internal/cmd/cmd.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +// Make constructs the "coder" root command +func Make() *cobra.Command { + app := &cobra.Command{ + Use: "coder", + Short: "coder provides a CLI for working with an existing Coder Enterprise installation", + } + + app.AddCommand( + makeLoginCmd(), + makeLogoutCmd(), + makeShellCmd(), + makeUsersCmd(), + makeConfigSSHCmd(), + makeSecretsCmd(), + makeEnvsCommand(), + makeSyncCmd(), + makeURLCmd(), + completionCmd, + genDocs(app), + ) + return app +} + +func genDocs(rootCmd *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "gen-docs [dir_path]", + Short: "Generate a markdown documentation tree for the root command.", + Example: "coder gen-docs ./docs", + Args: cobra.ExactArgs(1), + Hidden: true, + RunE: func(_ *cobra.Command, args []string) error { + return doc.GenMarkdownTree(rootCmd, args[0]) + }, + } + return cmd +} + +// reference: https://github.com/spf13/cobra/blob/master/shell_completions.md +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Example: `coder completion fish > ~/.config/fish/completions/coder.fish +coder completion zsh > "${fpath[1]}/_coder" + +Linux: + $ coder completion bash > /etc/bash_completion.d/coder +MacOS: + $ coder completion bash > /usr/local/etc/bash_completion.d/coder`, + Long: `To load completions: + +Bash: + +$ source <(coder completion bash) + +To load completions for each session, execute once: +Linux: + $ coder completion bash > /etc/bash_completion.d/coder +MacOS: + $ coder completion bash > /usr/local/etc/bash_completion.d/coder + +Zsh: + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions for each session, execute once: +$ coder completion zsh > "${fpath[1]}/_coder" + +You will need to start a new shell for this setup to take effect. + +Fish: + +$ coder completion fish | source + +To load completions for each session, execute once: +$ coder completion fish > ~/.config/fish/completions/coder.fish +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + cmd.Root().GenPowerShellCompletion(os.Stdout) + } + }, +} diff --git a/cmd/coder/configssh.go b/internal/cmd/configssh.go similarity index 83% rename from cmd/coder/configssh.go rename to internal/cmd/configssh.go index 50b991d4..1d1c3c7b 100644 --- a/cmd/coder/configssh.go +++ b/internal/cmd/configssh.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" @@ -7,6 +7,7 @@ import ( "net" "net/url" "os" + "os/user" "path/filepath" "strings" "time" @@ -29,13 +30,13 @@ func makeConfigSSHCmd() *cobra.Command { Long: "Inject the proper OpenSSH configuration into your local SSH config file.", RunE: configSSH(&configpath, &remove), } - cmd.Flags().StringVar(&configpath, "filepath", filepath.Join(os.Getenv("HOME"), ".ssh", "config"), "overide the default path of your ssh config file") + cmd.Flags().StringVar(&configpath, "filepath", filepath.Join("~", ".ssh", "config"), "overide the default path of your ssh config file") cmd.Flags().BoolVar(&remove, "remove", false, "remove the auto-generated Coder Enterprise ssh config") return cmd } -func configSSH(filepath *string, remove *bool) func(cmd *cobra.Command, _ []string) error { +func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []string) error { startToken := "# ------------START-CODER-ENTERPRISE-----------" startMessage := `# The following has been auto-generated by "coder config-ssh" # to make accessing your Coder Enterprise environments easier. @@ -51,12 +52,20 @@ func configSSH(filepath *string, remove *bool) func(cmd *cobra.Command, _ []stri ctx, cancel := context.WithCancel(context.Background()) defer cancel() - currentConfig, err := readStr(*filepath) + if strings.HasPrefix(*configpath, "~") { + usr, err := user.Current() + if err != nil { + return xerrors.Errorf("get user home directory: %w", err) + } + *configpath = strings.Replace(*configpath, "~", usr.HomeDir, 1) + } + + currentConfig, err := readStr(*configpath) if os.IsNotExist(err) { // SSH configs are not always already there. currentConfig = "" } else if err != nil { - return xerrors.Errorf("read ssh config file %q: %w", filepath, err) + return xerrors.Errorf("read ssh config file %q: %w", configpath, err) } startIndex := strings.Index(currentConfig, startToken) @@ -68,9 +77,9 @@ func configSSH(filepath *string, remove *bool) func(cmd *cobra.Command, _ []stri } currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(endToken)+1:] - err = writeStr(*filepath, currentConfig) + err = writeStr(*configpath, currentConfig) if err != nil { - return xerrors.Errorf("write to ssh config file %q: %v", *filepath, err) + return xerrors.Errorf("write to ssh config file %q: %v", *configpath, err) } return nil @@ -105,16 +114,20 @@ func configSSH(filepath *string, remove *bool) func(cmd *cobra.Command, _ []stri currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(endToken)+1:] } - err = writeStr(*filepath, currentConfig+newConfig) + err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm) + if err != nil { + return xerrors.Errorf("make configuration directory: %w", err) + } + err = writeStr(*configpath, currentConfig+newConfig) if err != nil { - return xerrors.Errorf("write new configurations to ssh config file %q: %w", filepath, err) + return xerrors.Errorf("write new configurations to ssh config file %q: %w", *configpath, err) } err = writeSSHKey(ctx, entClient) if err != nil { return xerrors.Errorf("fetch and write ssh key: %w", err) } - fmt.Printf("An auto-generated ssh config was written to %q\n", *filepath) + fmt.Printf("An auto-generated ssh config was written to %q\n", *configpath) fmt.Printf("Your private ssh key was written to %q\n", privateKeyFilepath) fmt.Println("You should now be able to ssh into your environment") fmt.Printf("For example, try running\n\n\t$ ssh coder.%s\n\n", envs[0].Name) diff --git a/cmd/coder/envs.go b/internal/cmd/envs.go similarity index 99% rename from cmd/coder/envs.go rename to internal/cmd/envs.go index 32d73245..4e3219b7 100644 --- a/cmd/coder/envs.go +++ b/internal/cmd/envs.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "encoding/json" diff --git a/cmd/coder/login.go b/internal/cmd/login.go similarity index 99% rename from cmd/coder/login.go rename to internal/cmd/login.go index ed8cb499..d81dac0e 100644 --- a/cmd/coder/login.go +++ b/internal/cmd/login.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "net" diff --git a/cmd/coder/logout.go b/internal/cmd/logout.go similarity index 97% rename from cmd/coder/logout.go rename to internal/cmd/logout.go index 079a2e56..621ed89e 100644 --- a/cmd/coder/logout.go +++ b/internal/cmd/logout.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "os" diff --git a/cmd/coder/secrets.go b/internal/cmd/secrets.go similarity index 99% rename from cmd/coder/secrets.go rename to internal/cmd/secrets.go index 9433588c..7303d8fa 100644 --- a/cmd/coder/secrets.go +++ b/internal/cmd/secrets.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" diff --git a/cmd/coder/shell.go b/internal/cmd/shell.go similarity index 99% rename from cmd/coder/shell.go rename to internal/cmd/shell.go index eca157e0..7cfee8b0 100644 --- a/cmd/coder/shell.go +++ b/internal/cmd/shell.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" diff --git a/cmd/coder/sync.go b/internal/cmd/sync.go similarity index 99% rename from cmd/coder/sync.go rename to internal/cmd/sync.go index 6cab1bc2..c41d06fd 100644 --- a/cmd/coder/sync.go +++ b/internal/cmd/sync.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "bytes" diff --git a/cmd/coder/urls.go b/internal/cmd/urls.go similarity index 99% rename from cmd/coder/urls.go rename to internal/cmd/urls.go index 7f29cdb8..ba6e5494 100644 --- a/cmd/coder/urls.go +++ b/internal/cmd/urls.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" diff --git a/cmd/coder/users.go b/internal/cmd/users.go similarity index 99% rename from cmd/coder/users.go rename to internal/cmd/users.go index d43d2f4b..c88dae47 100644 --- a/cmd/coder/users.go +++ b/internal/cmd/users.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "encoding/json" pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy