Skip to content

Wrapper around help text #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ these built-in.

### entrypoint

Most Gruntwork CLI apps should use this package to run their app, as it takes care of common tasks such as setting the
proper exit code, rendering stack traces, and handling panics. Note that this package assumes you are using
[urfave/cli](https://github.com/urfave/cli), which is currently our library of choice for CLI apps.
Most Gruntwork CLI apps should use this package to run their app, as it takes
care of common tasks such as setting the proper exit code, rendering stack
traces, handling panics, and rendering help text in a standard format. Note
that this package assumes you are using
[urfave/cli](https://github.com/urfave/cli), which is currently our library of
choice for CLI apps.

Here is the typical usage pattern in `main.go`:

Expand All @@ -45,8 +48,9 @@ import (
var VERSION string

func main() {
// Create a new CLI app with urfave/cli
app := cli.NewApp()
// Create a new CLI app. This will return a urfave/cli App with some
// common initialization.
app := entrypoint.NewApp()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to call out this change in the release notes!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, you mean https://github.com/gruntwork-io/gruntwork-cli/releases and newsletter and not a special file in here that tracks changes? Also, this should be a minor release (0.2.0) because it is a backwards compatible feature release right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes on all counts!


app.Name = "my-app"
app.Author = "Gruntwork <www.gruntwork.io>"
Expand Down Expand Up @@ -129,4 +133,4 @@ This package contains two types of helpers:

```
go test -v ./...
```
```
13 changes: 12 additions & 1 deletion entrypoint/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ package entrypoint

import (
"os"

"github.com/urfave/cli"

"github.com/gruntwork-io/gruntwork-cli/errors"
"github.com/gruntwork-io/gruntwork-cli/logging"
)

const defaultSuccessExitCode = 0
const defaultErrorExitCode = 1

// Wrapper around cli.NewApp that sets the help text printer.
func NewApp() *cli.App {
cli.HelpPrinter = WrappedHelpPrinter
cli.AppHelpTemplate = CLI_APP_HELP_TEMPLATE
cli.CommandHelpTemplate = CLI_COMMAND_HELP_TEMPLATE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, in some of our apps, we overwrite these help templates. E.g., Terragrunt: https://github.com/gruntwork-io/terragrunt/blob/master/cli/cli_app.go#L151 (note, we have not yet migrated Terragrunt to use gruntwork-cli under the hood, so it's just an example). Will the wrapped help text work for these apps?

Copy link
Contributor Author

@yorinasub17 yorinasub17 Oct 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should work. Actually, these are global variables in urfave/cli that are read in as part of the help command, so a later overwrites will take precedence. You just have to override after the NewApp call, as opposed to before:

cli := entrypoint.NewAll()
cli.AppHelpTemplate = HELP_TEMPLATE

That said, it doesn't handle the elastic tabstop output table so for that example the output is not so nice (pasted below). Once I switched the template to raw tabs, it looked better so may not be worth generalizing.

No tabs:

DESCRIPTION:
   terragrunt - Terragrunt is a thin wrapper for Terraform that provides extra
   tools for working with multiple
   Terraform modules, remote state, and locking. For documentation, see
   https://github.com/gruntwork-io/terragrunt/.

USAGE:
   terragrunt <COMMAND>

COMMANDS:
   plan-all             Display the plans of a 'stack' by running 'terragrunt
   plan' in each subfolder
   apply-all            Apply a 'stack' by running 'terragrunt apply' in each
   subfolder
   output-all           Display the outputs of a 'stack' by running 'terragrunt
   output' in each subfolder
   destroy-all          Destroy a 'stack' by running 'terragrunt destroy' in
   each subfolder
   validate-all         Validate 'stack' by running 'terragrunt validate' in
   each subfolder
   *                    Terragrunt forwards all other commands directly to
   Terraform

GLOBAL OPTIONS:
   terragrunt-config                    Path to the Terragrunt config file.
   Default is terraform.tfvars.
   terragrunt-tfpath                    Path to the Terraform binary. Default is
   terraform (on PATH).
   terragrunt-no-auto-init              Don't automatically run 'terraform init'
   during other terragrunt commands. You must run 'terragrunt init' manually.
   terragrunt-no-auto-retry             Don't automatically re-run command in
   case of transient errors.
   terragrunt-non-interactive           Assume "yes" for all prompts.
   terragrunt-working-dir               The path to the Terraform templates.
   Default is current directory.
   terragrunt-download-dir              The path where to download Terraform
   code. Default is .terragrunt-cache in the working directory.
   terragrunt-source                    Download Terraform configurations from
   the specified source into a temporary folder, and run Terraform in that
   temporary folder.
   terragrunt-source-update             Delete the contents of the temporary
   folder to clear out any old, cached source code before downloading new source
   code into it.
   terragrunt-iam-role                 Assume the specified IAM role
                                       before executing Terraform.
                                       Can also be set via the
                                       TERRAGRUNT_IAM_ROLE
                                       environment variable.
   terragrunt-ignore-dependency-errors  *-all commands continue processing
   components even if a dependency fails.
   terragrunt-exclude-dir               Unix-style glob of directories to
   exclude when running *-all commands

VERSION:


AUTHOR(S):
   Gruntwork <www.gruntwork.io>

with tabs

DESCRIPTION:                                                                                                                                         [65/7627]
   terragrunt - Terragrunt is a thin wrapper for Terraform that provides extra
   tools for working with multiple
   Terraform modules, remote state, and locking. For documentation, see
   https://github.com/gruntwork-io/terragrunt/.

USAGE:
   terragrunt <COMMAND>

COMMANDS:
   plan-all      Display the plans of a 'stack' by running 'terragrunt plan'
                 in each subfolder
   apply-all     Apply a 'stack' by running 'terragrunt apply' in each
                 subfolder
   output-all    Display the outputs of a 'stack' by running 'terragrunt
                 output' in each subfolder
   destroy-all   Destroy a 'stack' by running 'terragrunt destroy' in each
                 subfolder
   validate-all  Validate 'stack' by running 'terragrunt validate' in each
                 subfolder
   *             Terragrunt forwards all other commands directly to Terraform

GLOBAL OPTIONS:
   terragrunt-config                    Path to the Terragrunt config file. Default is
                                        terraform.tfvars.
   terragrunt-tfpath                    Path to the Terraform binary. Default is terraform
                                        (on PATH).
   terragrunt-no-auto-init              Don't automatically run 'terraform init'
                                        during other terragrunt commands. You must run
                                        'terragrunt init' manually.
   terragrunt-no-auto-retry             Don't automatically re-run command in case of
                                        transient errors.
   terragrunt-non-interactive           Assume "yes" for all prompts.
   terragrunt-working-dir               The path to the Terraform templates. Default is
                                        current directory.
   terragrunt-download-dir              The path where to download Terraform code.
                                        Default is .terragrunt-cache in the working
                                        directory.
   terragrunt-source                    Download Terraform configurations from the specified
                                        source into a temporary folder, and run Terraform in
                                        that temporary folder.
   terragrunt-source-update             Delete the contents of the temporary folder
                                        to clear out any old, cached source code
                                        before downloading new source code into it.
   terragrunt-iam-role                  Assume the specified IAM role before executing
                                        Terraform. Can also be set via the
                                        TERRAGRUNT_IAM_ROLE environment variable.
   terragrunt-ignore-dependency-errors  *-all commands continue processing
                                        components even if a dependency
                                        fails.
   terragrunt-exclude-dir               Unix-style glob of directories to exclude when
                                        running *-all commands

VERSION:


AUTHOR(S):
   Gruntwork <www.gruntwork.io>

app := cli.NewApp()
return app
}

// Run the given app, handling errors, panics, and stack traces where possible
func RunApp(app *cli.App) {
cli.OsExiter = func(exitCode int) {
Expand Down Expand Up @@ -38,4 +49,4 @@ func checkForErrorsAndExit(err error) {
}

os.Exit(exitCode)
}
}
219 changes: 219 additions & 0 deletions entrypoint/entrypoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package entrypoint

import (
"bytes"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
"testing"
)

func TestEntrypointNewAppWrapsAppHelpPrinter(t *testing.T) {
app := createSampleApp()
fakeStdout := bytes.NewBufferString("")
app.Writer = fakeStdout
args := []string{"houston", "help"}
err := app.Run(args)
assert.Nil(t, err)
assert.Equal(t, fakeStdout.String(), EXPECTED_APP_HELP_OUT)
}

func TestEntrypointNewAppWrapsCommandHelpPrinter(t *testing.T) {
app := createSampleApp()
fakeStdout := bytes.NewBufferString("")
app.Writer = fakeStdout
args := []string{"houston", "help", "exec"}
err := app.Run(args)
assert.Nil(t, err)
assert.Equal(t, fakeStdout.String(), EXPECTED_EXEC_CMD_HELP_OUT)
}

func TestEntrypointNewAppHelpPrinterHonorsLineWidthVar(t *testing.T) {
HelpTextLineWidth = 120
app := createSampleApp()
fakeStdout := bytes.NewBufferString("")
app.Writer = fakeStdout
args := []string{"houston", "help"}
err := app.Run(args)
assert.Nil(t, err)
assert.Equal(t, fakeStdout.String(), EXPECTED_APP_HELP_OUT_120_LINES)
}

func TestEntrypointNewAppCommandHelpPrinterHonorsLineWidthVar(t *testing.T) {
HelpTextLineWidth = 120
app := createSampleApp()
fakeStdout := bytes.NewBufferString("")
app.Writer = fakeStdout
args := []string{"houston", "help", "exec"}
err := app.Run(args)
assert.Nil(t, err)
assert.Equal(t, fakeStdout.String(), EXPECTED_EXEC_CMD_HELP_OUT_120_LINES)
}

func noop(c *cli.Context) error { return nil }

func createSampleApp() *cli.App {
app := NewApp()
app.Name = "houston"
app.HelpName = "houston"
app.Version = "v0.0.6"
app.Description = `A CLI tool for interacting with Gruntwork Houston that you can use to authenticate to AWS on the CLI and to SSH to your EC2 Instances.`

configFlag := cli.StringFlag{
Name: "config, c",
Value: "~/.houston/houston.yml",
Usage: "The configuration file for houston",
}

portFlag := cli.IntFlag{
Name: "port",
Value: 44444,
Usage: "The TCP port the http server is running on",
}

app.Commands = []cli.Command{
{
Name: "exec",
Usage: "Execute a command with temporary AWS credentials obtained by logging into Gruntwork Houston",
UsageText: "houston exec [options] <profile> -- <command>",
Description: `The exec command makes it easier to use CLI tools that need AWS credentials, such as aws, terraform, and packer. Here's how it works:

1. The first time you run this command for a <profile>, it will open your web browser and have you login to your Identity Provider (i.e., Google, ADFS, Okta).
2. After login, the Identity Provider will redirect you to the Gruntwork Houston web console, where you will pick the AWS IAM Role you want to use.
3. Gruntwork Houston will fetch temporary AWS credentials for this IAM Role and POST them back to houston CLI running on your computer.
4. The houston CLI will set those credentials as the appropriate environment variables and execute <command>.
5. The houston CLI will cache those credentials in memory, so all subsequent commands will execute without going through the login flow (until those credentials expire).

Examples:

houston exec dev -- aws s3 ls
houston exec prod -- terraform apply
houston exec stage -- packer build server.json`,
Action: noop,
Flags: []cli.Flag{configFlag, portFlag},
},
{
Name: "ssh",
Usage: "Connect to an EC2 instance via SSH with your public key.",
UsageText: "houston ssh [OPTIONS] <username>@<ip>",
Description: `Pre-requisites for using this command:

1. Install ssh-grunt on your EC2 Instances.
2. Configure each EC2 Instance to talk to your Gruntwork Houston deployment and to grant access to users with certain SSH Roles.
3. Have an admin grant you those SSH Roles in your Identity Provider (e.g., Google, ADFS, Okta).
4. Upload your public SSH key into Gruntwork Houston using its web console.

Once all the above is taken care of, here is how the houston ssh command works:

1. If you haven't logged into the Gruntwork Houston web console recently, it will pop open your web browser and ask you to login.
2. Gruntwork Houston will cache the SSH Roles you have for a short time period. The need to update this cache is why the houston ssh command exists!
3. ssh-grunt runs a cron job on your EC2 Instances that creates local OS users for Houston users with specific SSH Roles.
4. The houston CLI on your computer will execute your native ssh command to connect to the EC2 Instance.
5. When the SSH request comes into the EC2 Instance, ssh-grunt fetches your public SSH key from Gruntwork Houston and gives it to the SSH daemon for verification.

Examples:

houston ssh grunt@11.22.33.44`,
Action: noop,
Flags: []cli.Flag{configFlag, portFlag},
},
{
Name: "configure",
Usage: "Configure houston CLI options.",
UsageText: "houston configure [options]",
Description: `The configure command can be used to setup or update the houston configuration file. When you run this command with no arguments, it will prompt you with the minimum required options for getting the CLI up and running. The prompt will include the current value as a default if the configuration file exists.`,
Action: noop,
Flags: []cli.Flag{configFlag},
},
}
return app
}

const EXPECTED_APP_HELP_OUT = `Usage: houston [--help] [--version] command [options] [args]

A CLI tool for interacting with Gruntwork Houston that you can use to
authenticate to AWS on the CLI and to SSH to your EC2 Instances.

Commands:

exec Execute a command with temporary AWS credentials obtained by
logging into Gruntwork Houston
ssh Connect to an EC2 instance via SSH with your public key.
configure Configure houston CLI options.
help, h Shows a list of commands or help for one command

`

const EXPECTED_APP_HELP_OUT_120_LINES = `Usage: houston [--help] [--version] command [options] [args]

A CLI tool for interacting with Gruntwork Houston that you can use to authenticate to AWS on the CLI and to SSH to your
EC2 Instances.

Commands:

exec Execute a command with temporary AWS credentials obtained by logging into Gruntwork Houston
ssh Connect to an EC2 instance via SSH with your public key.
configure Configure houston CLI options.
help, h Shows a list of commands or help for one command

`

const EXPECTED_EXEC_CMD_HELP_OUT = `Usage: houston exec [options] <profile> -- <command>

The exec command makes it easier to use CLI tools that need AWS credentials,
such as aws, terraform, and packer. Here's how it works:

1. The first time you run this command for a <profile>, it will open your web
browser and have you login to your Identity Provider (i.e., Google, ADFS,
Okta).
2. After login, the Identity Provider will redirect you to the Gruntwork
Houston web console, where you will pick the AWS IAM Role you want to use.
3. Gruntwork Houston will fetch temporary AWS credentials for this IAM Role
and POST them back to houston CLI running on your computer.
4. The houston CLI will set those credentials as the appropriate environment
variables and execute <command>.
5. The houston CLI will cache those credentials in memory, so all subsequent
commands will execute without going through the login flow (until those
credentials expire).

Examples:

houston exec dev -- aws s3 ls
houston exec prod -- terraform apply
houston exec stage -- packer build server.json

Options:

--config value, -c value The configuration file for houston (default:
"~/.houston/houston.yml")
--port value The TCP port the http server is running on (default:
44444)

`

const EXPECTED_EXEC_CMD_HELP_OUT_120_LINES = `Usage: houston exec [options] <profile> -- <command>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests 👍


The exec command makes it easier to use CLI tools that need AWS credentials, such as aws, terraform, and packer. Here's
how it works:

1. The first time you run this command for a <profile>, it will open your web browser and have you login to your
Identity Provider (i.e., Google, ADFS, Okta).
2. After login, the Identity Provider will redirect you to the Gruntwork Houston web console, where you will pick the
AWS IAM Role you want to use.
3. Gruntwork Houston will fetch temporary AWS credentials for this IAM Role and POST them back to houston CLI running
on your computer.
4. The houston CLI will set those credentials as the appropriate environment variables and execute <command>.
5. The houston CLI will cache those credentials in memory, so all subsequent commands will execute without going
through the login flow (until those credentials expire).

Examples:

houston exec dev -- aws s3 ls
houston exec prod -- terraform apply
houston exec stage -- packer build server.json

Options:

--config value, -c value The configuration file for houston (default: "~/.houston/houston.yml")
--port value The TCP port the http server is running on (default: 44444)

`
Loading